变通方案:为 public/ 中的每个顶级文件和文件夹创建一个缓存行为(AWS 特定)
正如 资产文件 部分所述,你应用的 public/ 文件夹中的文件是静态的,并上传到 S3 存储桶。对这些文件的请求由 S3 存储桶处理,如下所示:
https://my-nextjs-app.com/favicon.ico
https://my-nextjs-app.com/my-images/avatar.png理想情况下,我们会创建一个单一的缓存行为,将所有对 public/ 文件的请求路由到 S3 存储桶。不幸的是,CloudFront 不支持缓存行为的正则表达式或高级字符串模式(即 /favicon.ico|my-images\/*/ )。
为了克服这个限制,我们为 public/ 中的每个顶级文件和文件夹创建一个单独的缓存行为。例如,如果你的文件夹结构是:
public/
favicon.ico
my-images/
avatar.png
avatar-dark.png
foo/
bar.png你将创建三个缓存行为:/favicon.ico、/my-images/* 和 /foo/*。这些行为中的每一个都指向 S3 存储桶。
需要注意的一点是,CloudFront 每个分发 默认限制为 25 个行为 (opens in a new tab)。如果你有很多顶级文件和文件夹,可能会达到这个限制。为了避免这种情况,考虑将部分或所有文件和文件夹移动到一个子目录中:
public/
files/
favicon.ico
my-images/
avatar.png
avatar-dark.png
foo/
bar.png在这种情况下,你只需要创建一个缓存行为:/files/*。
确保相应地更新你的代码以反映新的文件路径。
或者,你可以 通过 AWS 支持请求增加限制 (opens in a new tab)。
变通方案:设置 x-forwarded-host 标头(AWS 特定)
当服务器函数收到请求时,Lambda 请求标头中的 host 值被设置为 AWS Lambda 服务的主机名,而不是实际的前端主机名。当服务器函数(中间件、SSR 路由或 API 路由)需要知道前端主机时,这会产生问题。
为解决此问题,会在查看器请求(Viewer Request)上运行 CloudFront 函数,该函数将前端主机名设置为 x-forwarded-host 标头。函数代码如下所示:
function handler(event) {
var request = event.request;
request.headers["x-forwarded-host"] = request.headers.host;
return request;
}然后,服务器函数在向 NextServer 发送请求时,会将请求的 host 标头设置为 x-forwarded-host 标头的值。
变通方案:设置 NextRequest 地理位置数据
当你的应用程序托管在 Vercel 上时,你可以通过 NextRequest 对象在中间件中访问用户的地理位置。
export function middleware(request: NextRequest) {
request.geo.country;
request.geo.city;
}当你的应用程序托管在 AWS 上时,你可以 从 CloudFront 请求标头获取地理位置数据 (opens in a new tab)。但是,没有办法将此数据设置在传递给中间件函数的 NextRequest 对象上。
为解决此问题,NextRequest 构造函数被修改为从 CloudFront 标头初始化地理位置数据,而不是使用默认的空对象。
- geo: init.geo || {}
+ geo: init.geo || {
+ country: this.headers("cloudfront-viewer-country"),
+ countryName: this.headers("cloudfront-viewer-country-name"),
+ region: this.headers("cloudfront-viewer-country-region"),
+ regionName: this.headers("cloudfront-viewer-country-region-name"),
+ city: this.headers("cloudfront-viewer-city"),
+ postalCode: this.headers("cloudfront-viewer-postal-code"),
+ timeZone: this.headers("cloudfront-viewer-time-zone"),
+ latitude: this.headers("cloudfront-viewer-latitude"),
+ longitude: this.headers("cloudfront-viewer-longitude"),
+ metroCode: this.headers("cloudfront-viewer-metro-code"),
+ }CloudFront 提供更详细的地理位置信息,例如邮政编码和时区。以下是你的中间件中可用的 geo 属性的完整列表:
export function middleware(request: NextRequest) {
// Next.js 支持
request.geo.country;
request.geo.region;
request.geo.city;
request.geo.latitude;
request.geo.longitude;
// OpenNext 也支持
request.geo.countryName;
request.geo.regionName;
request.geo.postalCode;
request.geo.timeZone;
request.geo.metroCode;
}变通方案:NextServer 不为 HTML 页面设置缓存标头
正如 服务器函数 部分所述,服务器函数使用 Next.js 构建输出中的 NextServer 类来处理请求。但是,NextServer 似乎没有设置正确的 Cache Control 标头。
为解决此问题,服务器函数检查请求是否来自页面路由器的完全静态 HTML 页面(即没有 getStaticProps),并将 Cache Control 标头设置为:
public, max-age=0, s-maxage=31536000, must-revalidate如果你计划使用完全静态的 HTML 页面,你还应该将 x-middleware-prefetch 标头添加到 CloudFront 缓存标头中,以避免当设置此标头时 CloudFront 缓存空响应。
你也可以只是在页面中添加一个空的 getStaticProps 函数,这将设置正确的缓存标头。
变通方案:NextServer 未设置正确的 SWR 缓存标头
NextServer 似乎没有为 stale-while-revalidate 缓存标头设置合适的值。例如,标头可能如下所示:
s-maxage=600 stale-while-revalidate这会阻止 CloudFront 缓存陈旧数据。
为解决此问题,服务器函数检查响应是否包含 stale-while-revalidate 标头。如果找到,则将其值设置为 30 天:
s-maxage=600 stale-while-revalidate=2592000变通方案:设置 NextServer 工作目录(AWS 特定)
Next.js 建议使用 process.cwd() 而不是 __dirname 来获取应用目录。例如,考虑你的应用中有一个包含 markdown 文件的 posts 文件夹:
pages/
posts/
my-post.md
public/
next.config.js
package.json你可以这样构建文件路径:
path.join(process.cwd(), "posts", "my-post.md");正如 服务器函数 部分所述,在非 monorepo 设置中,server-function 包看起来像:
.next/
node_modules/
posts/
my-post.md <- 路径为 "posts/my-post.md"
index.mjs在这种情况下,path.join(process.cwd(), "posts", "my-post.md") 解析为正确的路径。
但是,当用户的应用位于 monorepo 内部时(即在 /packages/web),server-function 包看起来像:
packages/
web/
.next/
node_modules/
posts/
my-post.md <- 路径为 "packages/web/posts/my-post.md"
index.mjs
node_modules/
index.mjs在这种情况下,path.join(process.cwd(), "posts", "my-post.md") 无法解析。
为解决此问题,我们将服务器函数的工作目录更改为 .next/ 所在的位置,即 packages/web。
变通方案:设置 __NEXT_PRIVATE_PREBUNDLED_REACT 以使用预捆绑的 React
对于 Next.js 13.2 及更高版本,你需要显式设置 __NEXT_PRIVATE_PREBUNDLED_REACT 环境变量。虽然在撰写本文时未记录此环境变量,但你可以参考 Next.js 源代码来了解其用法:
在 standalone 模式下,我们没有分离的渲染 worker,因此如果同时使用了 app 和 pages,我们需要解析到预捆绑的 React 以确保 app 版本的正确性。
使用静态路径要求这些模块,以确保它们在 standalone 模式下构建应用时被 NFT 跟踪,因为我们现在是有条件地别名化它们,所以在构建时跟踪它们很棘手。
在每个请求上,我们尝试检测路由是使用 Pages Router 还是 App Router。如果使用 Pages Router,我们将 __NEXT_PRIVATE_PREBUNDLED_REACT 设置为 undefined,这意味着使用 node_modules 中的 React 版本。但是,如果使用 App Router,__NEXT_PRIVATE_PREBUNDLED_REACT 会被设置,并使用预捆绑的 React 版本。
变通方案:13.4.13+ 破坏性变更(middleware, redirect, rewrites)
Next.js 13.4.13 重构了中间件逻辑,使其不再在服务器处理程序中运行。相反,它们作为子线程中的 worker 执行,这引入了约 5 秒的不可接受的延迟。为了规避此问题,open-next 需要在处理服务器处理程序之前自己实现中间件处理程序。
我们引入了一个自定义的 esbuild 插件,以有条件地注入和覆盖代码来正确处理破坏性变更。
默认请求处理程序位于 adapters/plugins/default.ts
当 open-next 需要由于 Next.js 破坏兼容性而覆盖该实现时,build.ts 中的 createServerBundle 会确定适当的覆盖内容以替换 default.ts 文件的代码。