如何自托管你的 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 的逻辑(或使用外部包),你可能能够将此逻辑移动到布局作为服务器组件。例如,检查headers和重定向。你也可以使用 headers、cookies 或查询参数通过 next.config.js
进行重定向或重写。如果这不起作用,你也可以使用自定义服务器。
环境变量
Next.js 可以支持构建时和运行时环境变量。
默认情况下,环境变量仅在服务器上可用。要将环境变量暴露给浏览器,它必须以 NEXT_PUBLIC_
为前缀。但是,这些公共环境变量将在 next build
期间内联到 JavaScript 包中。
要读取运行时环境变量,我们建议使用 getServerSideProps
或逐步采用 App Router。
你可以在动态渲染期间安全地在服务器上读取环境变量。
- TypeScript
- JavaScript
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, 和其他动态 API
// 也会选择动态渲染,这意味着
// 这个环境变量在运行时被评估
const value = process.env.MY_VALUE
// ...
}
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, 和其他动态 API
// 也会选择动态渲染,这意味着
// 这个环境变量在运行时被评估
const value = process.env.MY_VALUE
// ...
}
这允许你使用一个单一的 Docker 镜像,可以通过具有不同值的多个环境进行推广。
需要了解:
- 你可以使用
register
函数在服务器启动时运行代码。- 我们不建议使用 runtimeConfig 选项,因为这不适用于独立输出模式。相反,我们建议逐步采用 App Router。
缓存和 ISR
Next.js 可以缓存响应、生成的静态页面、构建输出以及其他静态资产,如图像、字体和脚本。
缓存和重新验证页面(使用增量静态再生)使用相同的共享缓存。默认情况下,此缓存存储在你的 Next.js 服务器的文件系统(磁盘)上。这在自托管时自动工作,使用 Pages 和 App Router。
如果你希望将缓存的页面和数据持久化到持久存储,或在多个容器或 Next.js 应用程序实例之间共享缓存,你可以配置 Next.js 缓存位置。
自动缓存
- Next.js 为真正不可变的资产设置
Cache-Control
头为public, max-age=31536000, immutable
。它不能被覆盖。这些不可变文件在文件名中包含 SHA 哈希,因此可以安全地无限期缓存。例如,静态图像导入。你可以为图像配置 TTL。 - 增量静态再生 (ISR) 设置
Cache-Control
头为s-maxage: <revalidate in getStaticProps>, stale-while-revalidate
。此重新验证时间在你的getStaticProps
函数中以秒为单位定义。如果你设置revalidate: false
,它将默认为一年的缓存持续时间。 - 动态渲染的页面设置
Cache-Control
头为private, no-cache, no-store, max-age=0, must-revalidate
以防止用户特定数据被缓存。这适用于 App Router 和 Pages Router。这也包括草稿模式。
静态资产
如果你希望在不同的域或 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() // 将 tags 转换为数组
// 遍历缓存中的所有条目
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 hash
return process.env.GIT_HASH
},
}
版本偏差
Next.js 将自动缓解大多数版 本偏差实例,并在检测到时自动重新加载应用程序以检索新资产。例如,如果 deploymentId
不匹配,页面之间的转换将执行硬导航而不是使用预取的值。
当应用程序重新加载时,如果它没有设计为在页面导航之间持久化,可能会有应用程序状态丢失。例如,使用 URL 状态或本地存储将在页面刷新后持久化状态。但是,像 useState
这样的组件状态在这样的导航中会丢失。
流式传输和 Suspense
Next.js App Router 在自托管时支持流式响应。如果你使用 Nginx 或类似的代理,你需要配置它禁用缓冲以启用流式传输。
例如,你可以通过将 X-Accel-Buffering
设置为 no
来禁用 Nginx 中的缓冲:
module.exports = {
async headers() {
return [
{
source: '/:path*{/}?',
headers: [
{
key: 'X-Accel-Buffering',
value: 'no',
},
],
},
]
},
}
部分预渲染
部分预渲染(实验性)默认与 Next.js 一起工作,不是仅限 CDN 的功能。这包括作为 Node.js 服务器部署(通过 next start
)以及在与 Docker 容器一起使用时。
与 CDN 一起使用
当在你的 Next.js 应用程序前面使用 CDN 时,当访问动态 API 时,页面将包含 Cache-Control: private
响应头。这确保生成的 HTML 页面被标记为不可缓存。如果页面完全预渲染为静态,它将包含 Cache-Control: public
以允许页面在 CDN 上缓存。
如果你不需要静态和动态组件的混合,你可以使整个路由静态并在 CDN 上缓存输出 HTML。当运行 next build
时,如果不使用动态 API,这种自动静态优化是默认行为。
随着部分预渲染转向稳定,我们将通过部署适配器 API 提供支持。
after
after
在使用 next start
自托管时完全支持。
停止服务器时,通过发送 SIGINT
或 SIGTERM
信号并等待来确保优雅关闭。这允许 Next.js 服务器等待直到 after
内部使用的待处理回调函数或 Promise 完成。
手动优雅关闭
在自托管时,你可能希望在服务器在 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('Received SIGTERM: cleaning up')
process.exit(0)
})
process.on('SIGINT', () => {
console.log('Received SIGINT: cleaning up')
process.exit(0)
})
}