Sponsor
ntab.devntab.dev 提升效率的新标签页组件
点击查看
Menu

Deploying

恭喜,现在是时候部署到生产环境了。

你可以部署由 Vercel 托管的 Next.js,或者在 Node.js 服务器、Docker 镜像上自托管,甚至是静态 HTML 文件。使用 next start 部署时,支持所有 Next.js 功能。

生产构建

运行 next build 会生成一个针对生产环境优化的应用版本。基于你的页面创建 HTML、CSS 和 JavaScript 文件。JavaScript 会被编译,浏览器包会使用 Next.js 编译器进行压缩,以帮助实现最佳性能并支持所有现代浏览器

Next.js 生成一个标准的部署输出,可用于托管和自托管的 Next.js。这确保了两种部署方法都支持所有功能。在下一个主要版本中,我们将把这个输出转换为我们的 Build Output API 规范

由 Vercel 托管的 Next.js

Vercel,Next.js 的创建者和维护者,为你的 Next.js 应用提供托管基础设施和开发者体验平台。

部署到 Vercel 是零配置的,并提供额外的增强功能,用于全球范围内的可扩展性、可用性和性能。然而,自托管时仍然支持所有 Next.js 功能。

了解更多关于 Vercel 上的 Next.js免费部署一个模板 来尝试一下。

自托管

你可以通过三种不同的方式自托管 Next.js:

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

我们有社区维护的以下提供商的部署示例:

Node.js 服务器

Next.js 可以部署到任何支持 Node.js 的托管提供商。确保你的 package.json"build""start" 脚本:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

然后,运行 npm run build 来构建你的应用。最后,运行 npm run start 来启动 Node.js 服务器。这个服务器支持所有 Next.js 功能。

Docker 镜像

Next.js 可以部署到任何支持 Docker 容器的托管提供商。当部署到容器编排工具如 Kubernetes 或在任何云提供商中运行容器时,你可以使用这种方法。

  1. 在你的机器上安装 Docker
  2. 克隆我们的示例(或者多环境示例
  3. 构建你的容器:docker build -t nextjs-docker .
  4. 运行你的容器:docker run -p 3000:3000 nextjs-docker

通过 Docker 的 Next.js 支持所有 Next.js 功能。

静态 HTML 导出

Next.js 可以从静态站点或单页应用(SPA)开始,然后可选择稍后升级以使用需要服务器的功能。

由于 Next.js 支持这种静态导出,它可以部署和托管在任何能够提供 HTML/CSS/JS 静态资产的 Web 服务器上。这包括像 AWS S3、Nginx 或 Apache 这样的工具。

作为静态导出运行不支持需要服务器的 Next.js 功能。了解更多

值得注意的是:

功能

图片优化

通过 next/image图片优化在使用 next start 部署时可零配置自托管。如果你更喜欢有一个单独的服务来优化图片,你可以配置一个图片加载器

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

值得注意的是:

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

中间件

使用 next start 部署时,中间件可以零配置自托管。由于它需要访问传入的请求,因此在使用静态导出时不支持。

中间件使用的是所有可用 Node.js API 的子集运行时,以帮助确保低延迟,因为它可能在你应用的每个路由或资产之前运行。这个运行时不需要"在边缘"运行,可以在单区域服务器中工作。在多个区域运行中间件需要额外的配置和基础设施。

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

环境变量

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

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

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

app/page.ts
TypeScript
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 镜像,可以在具有不同值的多个环境中提升。

值得注意的是:

  • 你可以使用 register 函数在服务器启动时运行代码。
  • 我们不推荐使用 runtimeConfig 选项,因为这不适用于独立输出模式。相反,我们建议逐步采用 App Router。

缓存和 ISR

Next.js 可以缓存响应、生成的静态页面、构建输出和其他静态资产,如图片、字体和脚本。

缓存和重新验证页面(使用增量静态再生)使用相同的共享缓存。默认情况下,这个缓存存储在你的 Next.js 服务器的文件系统(磁盘)上。这在自托管时自动工作,无论是使用 Pages 还是 App Router。

如果你想将缓存页面和数据持久化到持久存储,或者在多个容器或实例之间共享缓存,可以配置 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, // 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 这样的组件状态会在这种导航中丢失。

Vercel 为 Next.js 应用提供额外的偏差保护,以确保即使在部署新版本后,旧版本的资产和功能对旧客户端仍然可用。

你可以在 next.config.js 文件中手动配置 deploymentId 属性,以确保每个请求使用 ?dpl 查询字符串或 x-deployment-id 头。

流式处理和 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。

after

使用 next start 自托管时完全支持 after

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

如果你想在自定义基础设施上使用 after,请查看你的提供商文档以查看对 after 的支持。

参考:为无服务器平台支持 after 在无服务器上下文中使用 after 需要在发送响应后等待异步任务完成。在 Next.js 和 Vercel 中,这是通过一个称为 waitUntil(promise) 的原语实现的,它延长了无服务器调用的生命周期,直到所有传递给 waitUntil 的承诺都已解决。

如果你希望你的用户能够运行 after,你将不得不提供 waitUntil 的实现,使其以类似的方式行为。

当调用 after 时,Next.js 将像这样访问 waitUntil

const RequestContext = globalThis[Symbol.for('@next/request-context')]
const contextValue = RequestContext?.get()
const waitUntil = contextValue?.waitUntil

这意味着 globalThis[Symbol.for('@next/request-context')] 应该包含一个像这样的对象:

type NextRequestContext = {
  get(): NextRequestContextValue | undefined
}
 
type NextRequestContextValue = {
  waitUntil?: (promise: Promise<any>) => void
}

这是实现的示例。

import { AsyncLocalStorage } from 'node:async_hooks'
 
const RequestContextStorage = new AsyncLocalStorage<NextRequestContextValue>()
 
// Define and inject the accessor that next.js will use
const RequestContext: NextRequestContext = {
  get() {
    return RequestContextStorage.getStore()
  },
}
globalThis[Symbol.for('@next/request-context')] = RequestContext
 
const handler = (req, res) => {
  const contextValue = { waitUntil: YOUR_WAITUNTIL }
  // Provide the value
  return RequestContextStorage.run(contextValue, () => nextJsHandler(req, res))
}