跳到内容

如何自托管你的 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 的逻辑(或使用外部包),你可能能够将此逻辑移动到布局作为服务器组件。例如,检查请求头重定向。你还可以使用请求头、cookie 或查询参数通过 next.config.js 进行重定向重写。如果这不起作用,你还可以使用自定义服务器

环境变量

Next.js 可以支持构建时和运行时环境变量。

默认情况下,环境变量仅在服务器上可用。要将环境变量暴露给浏览器,必须使用 NEXT_PUBLIC_ 前缀。但是,这些公共环境变量将在 next build 期间内联到 JavaScript 包中。

为了读取运行时环境变量,我们建议使用 getServerSideProps逐步采用 App Router

这允许您使用单个 Docker 镜像,该镜像可以通过具有不同值的多个环境进行推广。

须知

缓存和 ISR

Next.js 可以缓存响应、生成的静态页面、构建输出以及图像、字体和脚本等其他静态资源。

缓存和重新验证页面(通过增量静态再生 (ISR))使用相同的共享缓存。默认情况下,此缓存存储在你的 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 将拥有缓存的副本。为了防止由于缓存默认不共享而显示过期数据,你可以配置 Next.js 缓存以提供缓存处理程序并禁用内存缓存。

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

next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // disable default in-memory caching
}

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

cache-handler.js
const cache = new Map()
 
module.exports = class CacheHandler {
  constructor(options) {
    this.options = options
  }
 
  async get(key) {
    // This could be stored anywhere, like durable storage
    return cache.get(key)
  }
 
  async set(key, data, ctx) {
    // This could be stored anywhere, like durable storage
    cache.set(key, {
      value: data,
      lastModified: Date.now(),
      tags: ctx.tags,
    })
  }
 
  async revalidateTag(tags) {
    // tags is either a string or an array of strings
    tags = [tags].flat()
    // Iterate over all entries in the cache
    for (let [key, value] of cache) {
      // If the value's tags include the specified tag, delete this entry
      if (value.tags.some((tag) => tags.includes(tag))) {
        cache.delete(key)
      }
    }
  }
 
  // If you want to have temporary in memory cache for a single request that is reset
  // before the next request you can leverage this method
  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 () => {
    // This could be anything, using the latest git hash
    return process.env.GIT_HASH
  },
}

版本偏差

Next.js 会自动缓解大多数版本偏差情况,并在检测到时自动重新加载应用程序以获取新资产。例如,如果 deploymentId 不匹配,页面之间的转换将执行硬导航,而不是使用预取值。

当应用程序重新加载时,如果它没有设计为在页面导航之间持久化,则可能会丢失应用程序状态。例如,使用 URL 状态或本地存储将在页面刷新后持久化状态。但是,像 useState 这样的组件状态将在这种导航中丢失。

手动优雅停机

在自托管时,你可能希望在服务器在 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)
  })
}