跳到主要内容

如何自托管你的 Next.js 应用

部署你的 Next.js 应用时,你可能希望根据你的基础设施配置不同功能的处理方式。

🎥 观看: 了解更多关于自托管 Next.js 的信息 → YouTube (45 分钟)

图像优化

通过 next/image 进行的图像优化在使用 next start 部署时无需配置即可自托管工作。如果你希望有单独的服务来优化图像,你可以配置图像加载器

图像优化可以通过在 next.config.js 中定义自定义图像加载器与静态导出一起使用。请注意,图像在运行时优化,而不是在构建期间。

需要了解:

  • 在基于 glibc 的 Linux 系统上,图像优化可能需要额外配置以防止过度内存使用。
  • 了解更多关于优化图像的缓存行为以及如何配置 TTL。
  • 如果你愿意,你也可以禁用图像优化并仍然保留使用 next/image 的其他好处。例如,如果你自己单独优化图像。

中间件

中间件在使用 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

你可以在动态渲染期间安全地在服务器上读取环境变量。

app/page.ts
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 解析上花费额外时间的缺点。

了解更多关于 assetPrefix

配置缓存

默认情况下,生成的缓存资产将存储在内存中(默认为 50mb)和磁盘上。如果你使用容器编排平台(如 Kubernetes)托管 Next.js,每个 pod 都将有缓存的副本。为了防止显示过时数据,因为缓存默认不在 pod 之间共享,你可以配置 Next.js 缓存以提供缓存处理程序并禁用内存缓存。

要在自托管时配置 ISR/数据缓存位置,你可以在 next.config.js 文件中配置自定义处理程序:

next.config.js
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // 禁用默认的内存缓存
}

然后,在项目根目录创建 cache-handler.js,例如:

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 命令:

next.config.js
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 中的缓冲:

next.config.js
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 自托管时完全支持。

停止服务器时,通过发送 SIGINTSIGTERM 信号并等待来确保优雅关闭。这允许 Next.js 服务器等待直到 after 内部使用的待处理回调函数或 Promise 完成。

手动优雅关闭

在自托管时,你可能希望在服务器在 SIGTERMSIGINT 信号上关闭时运行代码。

你可以将环境变量 NEXT_MANUAL_SIG_HANDLE 设置为 true,然后在你的 _document.js 文件中为该信号注册一个处理程序。你需要在 package.json 脚本中直接注册环境变量,而不是在 .env 文件中。

需要了解:手动信号处理在 next dev 中不可用。

package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "NEXT_MANUAL_SIG_HANDLE=true next start"
}
}
pages/_document.js
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)
})
}