Next.js部署全指南:Vercel、自托管与Docker方案对比
AIAI Summary (BLUF)
本文详细介绍 Next.js 应用的部署方式,包括使用 Vercel 托管和自托管方案(Node.js 服务器、Docker 容器、静态导出)。生产构建通过 next build 生成优化版本,自托管时所有功能均受支持。同时涵盖图片优化和中间件在自托管环境下的注意事项。
核心洞察
这篇部署文档最让我意外的是,官方没有逼你选 Vercel前端部署平台,其 DESIGN.md 风格为黑白精准、使用 Geist 字体。,反而把自托管的路一条条铺好了。最实用的一点是:只要用 next start 启动,所有功能都在,想用 Docker、Kubernetes 还是普通的 Node.js 服务器通过 next start 启动的生产服务器,支持所有 Next.js 功能。都没问题。静态导出才需要额外确认功能支持,别一上来就牺牲灵活性。
核心结论
使用
next start自托管时,所有 Next.js基于React的现代全栈Web开发框架,支持服务端渲染和静态生成。 功能均可用;静态 HTML 导出会禁用依赖服务器的功能,但服务器组件仍支持静态导出。next/image图片优化在next start下零配置可用,按需优化并默认缓存 60 分钟(可通过images.minimumCacheTTL调整);若将 Next.js 嵌入自定义 Node.js 服务器,需手动处理/_next/image请求。自托管默认的数据缓存存于内存(上限 50MB)和磁盘
.next/cache,Kubernetes 等多实例场景各 Pod 缓存不共享,可通过自定义cacheHandler接入 Redis/S3 实现共享。多容器滚动发布时,Next.js 通过部署 ID 自动处理版本偏差:客户端请求携带部署 ID,检测到不一致时强制硬导航以对齐版本,副作用是
useState等组件内存状态会丢失。App Router 流式响应若前挂 Nginx 等反向代理,需设置
X-Accel-Buffering: no响应头,否则代理会缓冲整个响应,导致流式失效。
部署
恭喜,现在可以交付生产了。
部署方式有几条路。想省事可以直接上 Vercel,官方托管,不用做任何配置。想自己掌控,可以跑在 Node.js 服务器上,也可以用 Docker 镜像,甚至导出成静态 HTML 文件托管到任意 Web 服务器。只要是用 next start 启动的,Next.js 的完整功能都在。
生产构建
跑一遍 next build,得到的是优化过的生产版本。每个页面会生成对应的 HTML、CSS、JavaScript 文件,JS 会被编译器编译过,浏览器端打包文件也会被压缩。这套输出考虑到了性能,也照顾了现代浏览器的兼容性。
Next.js 的构建产物是一份标准部署输出,托管和自托管用的都是同一份东西。正因为这样,不管部署到哪个环境,功能不会有差异。官方计划在下个大版本里把这份输出迁移到构建输出 API 规范。
使用 Vercel 管理 Next.js
Vercel 就是 Next.js 背后的团队,框架是他们在维护。用 Vercel 托管等于基础设施和开发者体验都有人管了,部署不用做任何配置,全球范围内的扩容、可用性和性能还有额外优化。
不过别担心自托管会缺功能,所有 Next.js 功能在自托管环境下照样支持。
想试的话,可以直接用模板部署,也可以先看看 Vercel 上的 Next.js 文档。
自托管
自托管有三条路,按需选:
- Node.js 服务器
- Docker 容器将 Next.js 应用打包为 Docker 镜像,可部署到任何支持 Docker 的托管提供商或容器编排器。
- 静态导出
官方有一个 45 分钟的讲解视频,想深入了解自托管可以看看。
社区也维护了不少部署示例,覆盖了这些平台:Deno、DigitalOcean、Flightcontrol、Fly.io、GitHub Pages、Google Cloud Run、Railway、Render、SST。
Node.js 服务器
只要托管的服务商支持 Node.js,Next.js 就能跑。先把 package.json 里的脚本配好:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
然后 npm run build 构建,再用 npm run start 把 Node.js 服务器拉起来。这种部署方式下,Next.js 所有功能都是可用的。
Docker 镜像
只要服务商能跑 Docker 容器,Next.js 就能往上扔。Kubernetes 这类容器编排工具,或者任意云厂商的容器服务,都适用。
操作步骤也不复杂:
- 本机装好 Docker
- 把官方示例仓库克隆下来,或者用多环境那个示例
- 构建镜像:docker build -t nextjs-docker .
- 跑起来:docker run -p 3000:3000 nextjs-docker
通过 Docker 部署,Next.js 功能也是全的。
静态 HTML 导出
这条路比较特别。Next.js 可以先按静态站点或单页应用的方式跑起来,以后需要服务器能力了,再随时升级。
因为支持静态导出,产物可以扔到任何能托管静态文件的地方,比如 AWS S3、Nginx、Apache。
代价是,依赖服务器的 Next.js 功能,在这种模式下都不能用。具体哪些功能不支持,官方文档里有列表,动手之前先翻一眼。
静态导出也不是处处受限。文档专门确认了,服务器组件是支持静态导出的。在 App Router 里写的服务端组件,导出时完全没问题。
特性
自托管涉及的功能,文档一项一项列了支持情况。我按顺序过一遍,限制和坑会顺带说清楚。
图片优化
next/image 的图片优化功能,用 next start 启动时零配置就能自托管。想单独搞个图片优化服务,文档也留了口子,配置自定义加载器就行。
静态导出配自定义图片加载器,也能用图片优化。但图片是运行时才优化的,构建的时候不处理。
有几个细节容易踩坑。基于 glibc 的 Linux 系统上,图片优化可能需要附加配置,不然内存会吃得太狠。优化图片的缓存行为也值得看一眼,TTL 可以自己调。
你还可以直接禁用图片优化,next/image 的其它好处不受影响。比如你自己有一套图片处理流程,关掉反而省事。
中间件
中间件用 next start 也是零配置自托管。但它需要访问每一个传入的请求,所以静态导出用不了。
中间件跑在一个 Node.js API 子集的运行时上,放弃一部分 API 换低延迟。这个运行时不要求在边缘运行,单区域服务器就能跑。跑多区域,就得自己加配置和基础设施。
如果需要完整的 Node.js API,或者想用外部包,有个替代思路:把逻辑挪到 layout 里,写成服务器组件。检查 headers、做 redirect 都行。next.config.js 里也支持用 headers、cookie、query 参数判断,做 redirect 或 rewrite。要是还不够,就上定制服务器。
环境变量
构建时和运行时的环境变量都支持。默认情况下,环境变量只在服务端可用。要暴露给浏览器,必须加 NEXT_PUBLIC_ 前缀。但这些公共变量会在 next build 时被内联进 JavaScript bundle,敏感信息千万别放这里。
顺带说一句,服务端在动态渲染期间读 process.env 是安全的。文档给了个示例:在组件里先 await connection(),把渲染切成动态的,再读环境变量,拿到的就是运行时值。这个模式配合 Docker 很实用。打一个镜像,到不同环境用不同环境变量直接跑,不用重新构建。
还有两个点。可以用 register 函数在服务器启动时执行代码。官方不建议用 runtimeConfig 选项,这玩意儿在 standalone 输出模式下不工作。要迁移的话,建议渐进式切换到 App Router。
缓存和 ISR
Next.js 能缓存的东西不少:响应、生成的静态页面、构建输出,还有图片、字体、脚本这些静态资源。
页面缓存和 ISR 重新验证,用的是同一个共享缓存。默认情况下,这个缓存存在 Next.js 服务器的文件系统上,也就是磁盘。不管是 Pages Router 还是 App Router,自托管时这个机制都自动生效。
自托管涉及的功能,文档一项一项列了支持情况。我按顺序过一遍,限制和坑会顺带说清楚。
图片优化
next/image 的图片优化功能,用在小规模自托管场景下完全没问题。它是按需优化的,第一次请求图片时才生成优化版本,之后走缓存。默认缓存 60 分钟,可以通过 images.minimumCacheTTL 调整。
有个点需要注意:如果你用 next start 跑生产服务,图片优化是开箱即用的。但你要是把 Next.js 作为一个库嵌入到自己的 Node.js 服务器里,得手动处理图片优化请求。文档给了示例,用 imageOptimizer 中间件或者自己写路由都行,核心是把 /_next/image 这个路径的请求转发给图片优化逻辑。
另一个限制是优化后的图片默认只缓存到内存和磁盘,多实例部署时每台机器各自存一份。想共享图片缓存,得自己接 Redis 或者 S3,这个后面缓存部分会细说。
缓存
缓存这块分两块:数据缓存和构建缓存。
数据缓存就是 ISR 和 fetch 那套东西的落地存储。自托管时默认存在内存和磁盘上,内存上限 50MB,磁盘存在 .next/cache 目录里。
有个场景容易出问题:用 Kubernetes 这类容器编排平台部署时,每个 Pod 各自有一份缓存副本,Pod 之间不共享。结果就是同一个页面,打到 Pod A 可能走缓存,打到 Pod B 又重新生成一次。过时数据的问题倒不大,但缓存命中率会很难看,浪费 CPU。
文档给的解法是自定义 cacheHandler。在 next.config.js 里指定一个缓存处理模块:
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // 禁用默认的内存缓存
}
然后在项目根目录建 cache-handler.js,实现 get、set、revalidateTag 三个方法就行。官方示例里用的是 Map 存内存,实际生产环境可以接 Redis 或者 S3,保证所有 Pod 共享同一份缓存。
顺带说一句,revalidatePath 这个 API 是构建在缓存标签之上的。调用 revalidatePath 实际上会调用 revalidateTag,只不过为指定页面生成一个特殊的默认标签。理解这个关系之后,调试缓存失效问题会容易一些。
构建缓存
Next.js 在 next build 时生成一个构建 ID,用来标记当前应用的版本。多容器部署时,应该基于同一份构建产物启动所有实例。
如果每个环境都重新构建一次,比如开发环境构建一次、预发布环境再构建一次,那每个容器拿到的构建 ID 不一样。需要让构建 ID 保持一致的话,可以在 next.config.js 里指定 generateBuildId:
module.exports = {
generateBuildId: async () => {
// 可以用 git hash,也可以用环境变量
return process.env.GIT_HASH
},
}
这个 ID 会出现在客户端请求的资源路径里,保持一致可以避免不同容器之间出现资源版本不一致的问题。
版本偏差
多容器部署时,新旧版本同时存在的情况很难完全避免。比如滚动发布期间,老容器还没退完,新容器已经起来了。这时候用户请求打到不同容器上,拿到的页面版本可能不一样。
Next.js 对这个场景做了自动处理。页面组件里有个 deploymentId,客户端每次请求时会带上。部署 ID 对不上的话,页面之间的跳转会跳过预取数据,直接硬导航,强制拉一遍新资源。相当于自己给自己做了一次兜底的版本对齐。
代价是页面刷新时可能会丢应用状态。URL 参数或者 localStorage 里存的状态能保留下来,但 useState 这种组件内存状态在硬导航后肯定没了。如果应用依赖跨页面保留大量内存状态,这个行为要提前考虑到。
下一部分继续讲自托管的配置项和部署细节。
倾斜保护
新版本上线后,还停在旧页面的用户继续操作,请求打到新版本上,新旧代码混在一起容易出问题。Vercel 的倾斜保护就是干这个的,保证旧客户端在新版部署后依然能访问旧版本的资源和函数。
自托管没有这套机制,可以手动在 next.config.js 里配 deploymentId。请求带上 ?dpl 查询参数,或者加 x-deployment-id 请求头,就能让请求落到正确的版本上。
流式和悬念
App Router 自托管支持流式响应。前面挂 Nginx 这类代理的话,需要把缓冲关掉,不然代理会等整个响应读完再往下传,流式效果就没了。
关缓冲只需要设置 X-Accel-Buffering: no 响应头。在 next.config.js 里加一段配置:
module.exports = {
async headers() {
return [
{
source: '/:path*{/}?',
headers: [
{
key: 'X-Accel-Buffering',
value: 'no',
},
],
},
]
},
}
部分预渲染
部分预渲染(实验性)默认跟着 Next.js 走,不依赖 CDN。不管是通过 next start 跑 Node.js 服务器,还是放 Docker 容器里跑,行为都一样。
搭配 CDN 使用
套 CDN 的时候,页面访问了动态 API,响应头会带 Cache-Control: private,生成的 HTML 不会被 CDN 缓存。页面完全预渲染成静态的话,响应头是 Cache-Control: public,CDN 可以正常缓存。
不需要静态动态混合,就把整个路由做成静态,输出的 HTML 直接交给 CDN 缓存。只要不用动态 API,next build 默认就是这个行为。
after
after 在 next start 自托管下完全支持。
停服务器的时候发 SIGINT 或 SIGTERM 信号,等它优雅关闭。Next.js 会等到 after 里挂着的回调或 promise 全部跑完再退出。
想在自定义基础设施上用 after,先查服务商文档支不支持。serverless 的情况复杂一些,响应发完之后异步任务还得继续跑,平台得提供 waitUntil(promise) 这种原语,把函数生命周期延长到所有 promise 结束。Next.js 和 Vercel 就是这么配合的。
自己做平台的话,得实现一个行为类似的 waitUntil。Next.js 调用 after 时,通过全局对象访问它:
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>()
// 定义并注入 Next.js 要用的 accessor
const RequestContext: NextRequestContext = {
get() {
return RequestContextStorage.getStore()
},
}
globalThis[Symbol.for('@next/request-context')] = RequestContext
const handler = (req, res) => {
const contextValue = { waitUntil: YOUR_WAITUNTIL }
// 把 waitUntil 放进上下文里
return RequestContextStorage.run(contextValue, () => nextJsHandler(req, res))
}
常见问题(FAQ)
Next.js 部署到服务器一定要用 Vercel 吗?
不是。自托管完全可行,支持 Node.js 服务器、Docker 容器等方式。只要用 next start 启动,所有功能都可用。静态导出才会限制部分功能。
Next.js 自托管时图片优化和中间件怎么配置?
用 next start 启动时两者都零配置可用。但静态导出不支持中间件。图片优化在自托管时按需生成,可自定义加载器或调整缓存 TTL,注意内存配置。
Next.js 静态导出有什么限制?
依赖服务器的功能不可用,如中间件、ISR 等。但服务器组件支持静态导出。适合纯静态站点,未来可随时升级到完整功能。
版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。
文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。
若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。



