跳到内容

如何自托管您的 Next.js 应用程序

部署您的 Next.js 应用程序时,您可能需要根据您的基础设施配置如何处理不同的功能。

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

图片优化

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

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

须知

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

代理

当使用 next start 进行部署时,代理在自托管环境下无需任何配置即可工作。由于它需要访问传入请求,因此在使用静态导出时不支持。

代理使用Edge Runtime,它是可用 Node.js API 的子集,有助于确保低延迟,因为它可能在应用程序的每个路由或资产之前运行。如果您不希望这样,可以使用完整的 Node.js Runtime 来运行代理。

如果您希望添加需要所有 Node.js API 的逻辑(或使用外部包),您可以将此逻辑移动到布局中作为服务器组件。例如,检查头信息重定向。您还可以使用头信息、cookie 或查询参数通过 next.config.js 进行重定向重写。如果这不起作用,您还可以使用自定义服务器

环境变量

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

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

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

app/page.ts
import { connection } from 'next/server'
 
export default async function Component() {
  await connection()
  // cookies, headers, and other Dynamic APIs
  // will also opt into dynamic rendering, meaning
  // this env variable is evaluated at runtime
  const value = process.env.MY_VALUE
  // ...
}

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

须知

缓存和 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 都将拥有缓存的副本。为了防止由于缓存默认情况下不共享而显示过期数据,您可以配置 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 这样的组件状态将在这种导航中丢失。

流式传输和 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。当不使用动态 API 时,这种自动静态优化是运行 next build 时的默认行为。

随着局部预渲染趋于稳定,我们将通过部署适配器 API 提供支持。

after

当使用 next start 自托管时,after 得到完全支持。

当停止服务器时,请通过发送 SIGINTSIGTERM 信号并等待来确保优雅关机。这允许 Next.js 服务器等待 after 中使用的挂起回调函数或 Promise 完成。