如何自托管你的 Next.js 应用
当部署 Next.js 应用时,您可能需要根据基础设施配置不同功能的处理方式。
🎥 观看视频: 了解更多关于自托管 Next.js 的内容 → YouTube (45分钟)。
图片优化
通过 next/image
实现的图片优化在使用 next start
部署时可零配置自托管。如果您希望使用单独的服务优化图片,可以配置图片加载器。
通过静态导出时,可在 next.config.js
中定义自定义图片加载器来使用图片优化。请注意图片是在运行时优化,而非构建时。
须知:
中间件
中间件在使用 next start
部署时可零配置自托管。由于需要访问传入请求,静态导出时不支持中间件。
中间件使用 Edge 运行时,这是所有可用 Node.js API 的子集,有助于确保低延迟,因为它可能在应用的每个路由或资源前运行。如果不希望如此,可以使用完整的 Node.js 运行时运行中间件。
如果需要添加使用全部 Node.js API 的逻辑(或外部包),可将此逻辑移至布局作为服务器组件。例如,检查头信息和重定向。也可以通过 next.config.js
使用头信息、cookie 或查询参数进行重定向或重写。如果不可行,还可使用自定义服务器。
环境变量
Next.js 支持构建时和运行时环境变量。
默认情况下,环境变量仅在服务器端可用。要在浏览器中暴露环境变量,必须添加 NEXT_PUBLIC_
前缀。但这些公共环境变量会在 next build
期间内联到 JavaScript 包中。
要读取运行时环境变量,建议使用 getServerSideProps
或逐步采用应用路由器。
这允许您使用单一的 Docker 镜像,在不同环境中使用不同的值进行升级。
须知:
- 您可以使用
register
函数在服务器启动时运行代码。- 不建议使用 runtimeConfig 选项,因为它不适用于独立输出模式。建议逐步采用应用路由器。
缓存与 ISR
Next.js 可以缓存响应、生成的静态页面、构建输出和其他静态资源如图片、字体和脚本。
缓存和重新验证页面(使用增量静态再生)使用相同的共享缓存。默认情况下,此缓存存储在 Next.js 服务器的文件系统(磁盘)上。这在自托管时自动工作,适用于页面路由器和应用路由器。
如果需要将缓存页面和数据持久化到持久存储,或在多个容器或 Next.js 应用实例间共享缓存,可以配置 Next.js 缓存位置。
自动缓存
- Next.js 为真正不可变的资源设置
Cache-Control
头为public, max-age=31536000, immutable
。无法覆盖。这些不可变文件在文件名中包含 SHA 哈希,因此可以安全地无限期缓存。例如,静态图片导入。可以配置 TTL 用于图片。 - 增量静态再生 (ISR) 设置
Cache-Control
头为s-maxage: <在 getStaticProps 中重新验证>, stale-while-revalidate
。此重新验证时间在getStaticProps
函数中以秒为单位定义。如果设置revalidate: false
,则默认为一年缓存时长。 - 动态渲染的页面设置
Cache-Control
头为private, no-cache, no-store, max-age=0, must-revalidate
以防止用户特定数据被缓存。这适用于应用路由器和页面路由器。也包括草稿模式。
静态资源
如果希望在不同域名或 CDN 上托管静态资源,可以在 next.config.js
中使用 assetPrefix
配置。Next.js 在获取 JavaScript 或 CSS 文件时将使用此资源前缀。将资源分离到不同域名的缺点是额外的 DNS 和 TLS 解析时间。
配置缓存
默认情况下,生成的缓存资源将存储在内存(默认为 50mb)和磁盘上。如果使用 Kubernetes 等容器编排平台托管 Next.js,每个 pod 都会有缓存的副本。为防止显示过时数据(因为默认情况下缓存不在 pod 间共享),可以配置 Next.js 缓存以提供缓存处理程序并禁用内存缓存。
自托管时配置 ISR/数据缓存位置,可在 next.config.js
文件中配置自定义处理程序:
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // 禁用默认内存缓存
}
然后,在项目根目录创建 cache-handler.js
,例如:
const cache = new Map()
module.exports = class CacheHandler {
constructor(options) {
this.options = options
}
async get(key) {
// 可以存储在任何地方,如持久存储
return cache.get(key)
}
async set(key, data, ctx) {
// 可以存储在任何地方,如持久存储
cache.set(key, {
value: data,
lastModified: Date.now(),
tags: ctx.tags,
})
}
async revalidateTag(tags) {
// tags 是字符串或字符串数组
tags = [tags].flat()
// 遍历缓存中的所有条目
for (let [key, value] of cache) {
// 如果值的标签包含指定标签,删除此条目
if (value.tags.some((tag) => tags.includes(tag))) {
cache.delete(key)
}
}
}
// 如果想为单个请求设置临时内存缓存,并在下一个请求前重置,可以使用此方法
resetRequestCache() {}
}
使用自定义缓存处理程序可确保所有托管 Next.js 应用的 pod 间的一致性。例如,可以将缓存值保存在任何地方,如 Redis 或 AWS S3。
须知:
revalidatePath
是缓存标签上的便利层。调用revalidatePath
将使用提供的页面的特殊默认标签调用revalidateTag
函数。
构建缓存
Next.js 在 next build
期间生成 ID 以标识正在服务的应用版本。相同的构建应被使用并在多个容器中启动。
如果为环境的每个阶段重新构建,需要在容器间生成一致的构建 ID。在 next.config.js
中使用 generateBuildId
命令:
module.exports = {
generateBuildId: async () => {
// 可以是任何内容,使用最新的 git 哈希
return process.env.GIT_HASH
},
}
版本偏差
Next.js 会自动缓解大多数版本偏差实例,并在检测到不匹配时自动重新加载应用以获取新资源。例如,如果 deploymentId
不匹配,页面间转换将执行硬导航而非使用预取值。
当应用重新加载时,如果未设计为在页面导航间持久化,可能会丢失应用状态。例如,使用 URL 状态或本地存储将在页面刷新后保持状态。但组件状态如 useState
将在此类导航中丢失。
手动优雅关闭
自托管时,可能希望在服务器因 SIGTERM
或 SIGINT
信号关闭时运行代码。
可以设置环境变量 NEXT_MANUAL_SIG_HANDLE
为 true
,然后在 _document.js
文件中为该信号注册处理程序。需要直接在 package.json
脚本中注册环境变量,而非在 .env
文件中。
须知:手动信号处理在
next dev
中不可用。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "NEXT_MANUAL_SIG_HANDLE=true next start"
}
}
if (process.env.NEXT_MANUAL_SIG_HANDLE) {
process.on('SIGTERM', () => {
console.log('收到 SIGTERM:清理中')
process.exit(0)
})
process.on('SIGINT', () => {
console.log('收到 SIGINT:清理中')
process.exit(0)
})
}