我有一个使用 NextJS 作为包装器的应用程序,并且我利用了 NextJS 的动态路由功能。我在将其部署到 CloudFront 时遇到了问题,因为
dns.com/path/page
未渲染,而 CloudFront 期望它是 dns.com/path/page.html
。我通过应用这个 lambda-edge-nice-url 解决方案解决了这个问题。现在可以正常工作了。然而,仍然存在一个问题:NextJS 的动态路由。 dsn.com/path/subpath/123
应该可以工作,因为 123 是一个动态参数。然而,这不起作用。仅当我访问 dns.com/path/subpath/[id]
时才返回页面,这当然是不正确的,因为 [id] 不是我要加载的参数。
最奇怪的是:如果我尝试直接访问上面所说的 URL,就会失败。但是,在应用程序内部,我有可以重定向用户的按钮和链接,并且可以正常工作。
从应用程序内部导航(回调中带有
router.push
的按钮):
任何人都可以帮助我正确路由请求吗?
我使用 CloudFront Lambda@Edge 源请求函数来处理将动态路由和静态路由重写到适当的 HTML 文件,以便 CloudFront 可以为任何路径提供预期文件。
我的 lambda 函数看起来像
export const handler: CloudFrontRequestHandler = async (event) => {
const eventRecord = event.Records[0];
const request = eventRecord.cf.request;
const uri = request.uri;
// handle /posts/[id] dynamic route
if (uri === '/posts' || uri.startsWith('/posts/')) {
request.uri = "/posts/[id].html";
return request;
}
// if URI includes ".", indicates file extension, return early and don't modify URI
if (uri.includes('.')) {
return request;
}
// if URI ends with "/" slash, then we need to remove the slash first before appending .html
if (uri.endsWith('/')) {
request.uri = request.uri.substring(0, request.uri.length - 1);
}
request.uri += '.html';
return request;
};
在尝试了很多不同的代码之后,我终于想出了一个 Lambda 边缘表达式,它同时解决了两个问题:
.html
下面的代码基本上首先处理动态路由。它使用正则表达式来理解当前 URL 并将请求重定向到正确的
[id].html
文件。之后,如果没有任何正则表达式匹配,并且 URL 不包含 .html
扩展名,则会添加扩展名并检索正确的文件。
const config = {
suffix: '.html',
appendToDirs: 'index.html',
removeTrailingSlash: false,
};
const regexSuffixless = /\/[^/.]+$/; // e.g. "/some/page" but not "/", "/some/" or "/some.jpg"
const regexTrailingSlash = /.+\/$/; // e.g. "/some/" or "/some/page/" but not root "/"
const dynamicRouteRegex = /\/subpath\/\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/; // e.g /urs/some-uuid; // e.g. '/subpath/uuid'
exports.handler = function handler(event, context, callback) {
const { request } = event.Records[0].cf;
const { uri } = request;
const { suffix, appendToDirs, removeTrailingSlash } = config;
//Checks for dynamic route and retrieves the proper [id].html file
if (uri.match(dynamicRouteRegex)) {
request.uri = "/subpath/[id].html";
callback(null, request);
return;
}
// Append ".html" to origin request
if (suffix && uri.match(regexSuffixless)) {
request.uri = uri + suffix;
callback(null, request);
return;
}
// Append "index.html" to origin request
if (appendToDirs && uri.match(regexTrailingSlash)) {
request.uri = uri + appendToDirs;
callback(null, request);
return;
}
// Redirect (301) non-root requests ending in "/" to URI without trailing slash
if (removeTrailingSlash && uri.match(/.+\/$/)) {
const response = {
// body: '',
// bodyEncoding: 'text',
headers: {
'location': [{
key: 'Location',
value: uri.slice(0, -1)
}]
},
status: '301',
statusDescription: 'Moved Permanently'
};
callback(null, response);
return;
}
// If nothing matches, return request unchanged
callback(null, request);
};
非常感谢@LongZheng 的回答。由于某种原因,他的代码对我不起作用,但可能对某些人有用,所以请检查他的答案。另外,要向 Manc 表示大力的赞扬,他是这个 lambda-edge-nice-urls 存储库 的创建者。我的代码基本上是两者的混合。
@Pelicer 提到的解决方案并没有真正超出其解决方案的可扩展性,并且限制了您命名路径参数的方式。相反,类似的方法是使用动态生成的路线文件。使用 NextJS,如果您运行构建命令,它将在
out/.next/routes-manifest.json
处输出路由清单。该文件看起来像
{
"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": []}
这为我们提供了 nextjs 生成的动态路由,以便在静态生成的应用程序中使用。然后,我们可以编写一个简单的 CloudFront Lambda@Edge 函数,以便在请求传入时快速映射请求。以下代码将读取上述 json 清单并将请求重新路由到正确的 S3 路径。
注意:可以在静态和动态路由之间添加一些额外的重用。
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);};
2021 年,AWS 推出了 CloudFront Functions (https://aws.amazon.com/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any -规模/) 这比 Lambda@Edge 便宜得多,并且有大量的免费配额。
使用 terraform 你可以做类似的事情:
resource "aws_cloudfront_function" "rewrite_uri" {
name = "rewrite_uri"
runtime = "cloudfront-js-1.0"
comment = "Implement dynamic routes for Next.js"
publish = true
code = <<EOF
function handler(event) {
var request = event.request;
request.uri = request.uri.replace(/^\/something\/[^/]*\/edit$/, "/something/[something_id]/edit");
return request;
}
EOF
}
resource "aws_cloudfront_distribution" "page" {
...
default_cache_behavior {
....
function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.rewrite_uri.arn
}
}
}