The solution mentioned by @Pelicer is not really scalable beyond their solution and restricts how you name your path params. Instead a similar approach would be to use a dynamically generated file of routes. With NextJS if you run the build command it will output a routes manifest at out/.next/routes-manifest.json. This file will look something like
{
"version": 3,
"pages404": true,
"basePath": "",
"redirects": [
{
"source": "/:file((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/]+\\.\\w+)/",
"destination": "/:file",
"internal": true,
"statusCode": 308,
"regex": "^(?:/((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/]+\\.\\w+))/$"
},
{
"source": "/:notfile((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/\\.]+)",
"destination": "/:notfile/",
"internal": true,
"statusCode": 308,
"regex": "^(?:/((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/\\.]+))$"
}
],
"headers": [],
"dynamicRoutes": [
{
"page": "/test-path/[testPathId]",
"regex": "^/test\\-path/([^/]+?)(?:/)?$",
"routeKeys": {
"testPathId": "testPathId"
},
"namedRegex": "^/test\\-path/(?<testPathId>[^/]+?)(?:/)?$"
}
],
"staticRoutes": [
{
"page": "/",
"regex": "^/(?:/)?$",
"routeKeys": {},
"namedRegex": "^/(?:/)?$"
},
{
"page": "/home",
"regex": "^/home(?:/)?$",
"routeKeys": {},
"namedRegex": "^/home(?:/)?$"
}
],
"dataRoutes": [],
"rsc": {
"header": "RSC",
"varyHeader": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
},
"rewrites": []}
This gives us the dynamic routes that nextjs generates for use in the static generated app. We can then write a simple CloudFront Lambda@Edge function to quickly map the request when it comes in. The following code would read the above json manifest and reroute the request to the correct S3 path.
Note: There is some additional reuse that could be added here between the static and dynamic routes.
exports.handler = function (event, context, callback) {
let routes = require('./routes-manifest.json');
const { request } = event.Records[0].cf;
const { uri } = request;
const {dynamicRoutes, staticRoutes} = routes;
const appendToDirs = 'index.html';
if(!uri || uri === '/' || uri === ''){
callback(null, request);
return;
}
dynamicRoutes.forEach(route => {
if(uri.match(route.regex)){
if(uri.charAt(-1) === "/"){
request.uri = route.page + appendToDirs;
} else {
request.uri = route.page + "/" + appendToDirs;
}
callback(null, request);
return;
}
});
staticRoutes.forEach(route => {
if(uri.match(route.regex)){
if(uri.charAt(-1) === "/"){
request.uri = route.page + appendToDirs;
} else {
request.uri = route.page + "/" + appendToDirs;
}
callback(null, request);
return;
}
});
// If nothing matches, return request unchanged
callback(null, request);};