部署 — CodeRoadMap部署
这一课你将能做到
- 用 Vercel 一键部署 Next.js 项目,配置自动预览和域名
- 用 Docker 构建 Next.js 容器镜像,在任何云平台运行
- 正确管理环境变量(开发/预览/生产)
- 配置 GitHub Actions CI/CD 流水线,自动构建和部署
- 用 ISR 预热和自定义服务器优化生产环境性能
为什么要学
「能本地跑」和「能在生产跑」是两回事。本地开发时 dev server 帮你处理热更新、内存缓存、错误兜底,生产环境面对的是真实流量、真实网络、真实安全威胁。部署环节出问题,常见的坑有:环境变量在构建时不可用、静态资源 CDN 缓存策略错误、Docker 镜像体积过大、ISR 第一次访问超时……
人话:写好代码是 50%,部署好是另外 50%。很多项目死在「开发环境没问题,上线就崩」这一步。
先建立图像
部署选项对比:
├── Vercel(推荐) ← 原生支持 Next.js,自动 CDN、自动 ISR、零配置
├── Docker + 云 ← 通用方案,AWS/阿里云/腾讯云都能跑
└── 自定义服务器 ← next start,放在已有服务器或 Kubernetes 上
环境变量层级:
├── .env.local ← 本地开发,不上传 Git
├── .env.development ← 开发环境(npm run dev)
├── .env.production ← 生产环境(npm run build)
└── Vercel/CI 平台变量 ← 线上运行时注入
跟着做
步骤 1:Vercel 一键部署
npm install -g vercel
vercel login
在项目根目录执行:
vercel
按照提示完成:
- 确认项目名称
- 选择作用域(个人或团队)
- 选择根目录
- 设置构建命令(默认
next build)
部署完成后会输出预览 URL,如 https://my-app-xxx.vercel.app。
推送到 GitHub 后,Vercel 自动关联:
main 分支 → 生产环境
feature/* 分支 → 预览环境(自动生成 PR 预览链接)
步骤 2:环境变量配置
本地开发用 .env.local:
DATABASE_URL="postgresql://user:pass@localhost:5432/db"
NEXT_PUBLIC_API_URL="http://localhost:3000"
JWT_SECRET="dev-secret-key"
规则:NEXT_PUBLIC_ 前缀的变量会在构建时注入到客户端 JS;无前缀的变量只在服务端可用。
Vercel Dashboard → 项目设置 → Environment Variables:
DATABASE_URL:生产数据库连接串
NEXT_PUBLIC_API_URL:https://my-app.vercel.app
JWT_SECRET:生产密钥
不同环境(Production / Preview / Development)分别配置。
在代码中使用:
// 服务端可用
const dbUrl = process.env.DATABASE_URL;
// 客户端可用(必须有 NEXT_PUBLIC_ 前缀)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
步骤 3:Docker 部署
# 1. 依赖安装
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 2. 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build
# 3. 生产运行
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
next.config.ts 启用 standalone 输出:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;
docker build -t my-next-app .
docker run -p 3000:3000 my-next-app
standalone 模式只打包运行必需的文件,镜像体积从几百 MB 降到几十 MB。
步骤 4:GitHub Actions CI/CD
.github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test
- run: npm run build
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: vercel/action-deploy@v1
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
Vercel 的 Token 在 Vercel Dashboard → Settings → Tokens 获取。
步骤 5:ISR 预热
ISR 页面在第一次访问后才生成,第一个用户会等较长时间。用 cron 任务或部署后脚本预热:
// scripts/warm-isr.ts
async function warmCache() {
const urls = [
'https://my-app.vercel.app/blog/post-1',
'https://my-app.vercel.app/blog/post-2',
'https://my-app.vercel.app/products/a',
'https://my-app.vercel.app/products/b',
];
for (const url of urls) {
await fetch(url);
console.log('预热:', url);
}
}
warmCache();
vercel cron add "0 2 * * *" node scripts/warm-isr.ts
或在 GitHub Actions 里加定时任务:
on:
schedule:
- cron: '0 2 * * *' # 每天 2 点
步骤 6:自定义服务器(进阶)
如果需要在 Next.js 之前加自定义逻辑(如鉴权、日志、代理):
// server.ts
import { createServer } from 'http';
import next from 'next';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
// 自定义中间件逻辑
if (req.url?.startsWith('/api/')) {
res.setHeader('X-Custom-Header', 'hello');
}
handle(req, res);
}).listen(3000, () => {
console.log('Ready on http://localhost:3000');
});
});
自定义服务器会禁用一些 Next.js 优化(如自动静态优化),只在确实需要自定义 HTTP 层时使用。
讲清楚要点
- Vercel 是 Next.js 的最佳归宿:Vercel 是 Next.js 的开发团队,原生支持所有特性(ISR、Edge Runtime、Middleware),CDN 全球分发,自动 HTTPS,分支预览。
- 环境变量区分前后端:
NEXT_PUBLIC_ 前缀的变量注入客户端,适合 API 地址、地图 key;无前缀的只在服务端,适合数据库密码、JWT 密钥。
- Docker standalone 模式:
output: 'standalone' 只打包运行必需文件,不打包 devDependencies,镜像体积最小。
- ISR 预热是生产必做:否则第一个访问者承受「构建 + 渲染」的双重延迟。用定时任务或部署后脚本触发一次请求。
- 自定义服务器要慎用:它绕过了 Next.js 的自动优化(静态 HTML 优化),只在需要自定义 HTTP 层(如 WebSocket、特殊代理)时使用。
你可能会懵的地方
-
.env.local 能提交到 Git 吗?
不能。.env.local 在 .gitignore 里,包含敏感信息。提交到 Git 是安全事故。
-
process.env 在 Client Component 里能读到吗?
只有 NEXT_PUBLIC_ 前缀的可以。非前缀的变量在构建时被 tree-shaking 掉,客户端读不到。
-
Vercel 的 Hobby 方案有冷启动吗?
有。Hobby(免费)方案的 Serverless Function 在空闲后会休眠,第一次请求有冷启动(几百毫秒)。Pro 方案可以选预热或 Always On。
-
Docker 部署和 Vercel 部署哪个好?
团队小、快上线、不在乎厂商锁定 → Vercel;需要自定义基础设施、已有云厂商合同、需要跑长期进程 → Docker。
-
ISR 预热怎么确认生效?
预热后第一次请求应该返回 200 且响应时间 <200ms。如果还是慢,检查预热 URL 是否正确、CDN 缓存是否命中。
错法对照
| 错法 | 问题 | 正解 |
|---|
把 .env.local 提交到 Git | 敏感信息泄露 | 加入 .gitignore,用平台环境变量 |
在 Client Component 里读 process.env.DB_URL | 服务端变量,客户端 undefined | 加 NEXT_PUBLIC_ 前缀或只在服务端用 |
Docker 镜像没开 standalone | 镜像几百 MB | output: 'standalone' |
| 部署后不预热 ISR | 第一个用户等 5 秒 | 定时任务预热关键页面 |
| 自定义服务器滥用 | 失去 Next.js 自动优化 | 只在确实需要自定义 HTTP 层时用 |
卡点急救
- Vercel 构建报错
Cannot find module:检查 package.json 的依赖是否完整;Vercel 构建时不会装 devDependencies,如果 build 脚本依赖 devDep,加到 dependencies 或用 --include=dev。
- 环境变量在构建时不生效:Vercel 的环境变量在 Build 阶段可用,但如果是
NEXT_PUBLIC_ 变量在构建后修改,需要重新构建才能生效。
- Docker 容器启动后 404:检查
output: 'standalone' 是否配置;检查 Dockerfile 里 .next/standalone 路径是否正确。
- ISR 页面更新不生效:检查
revalidate 配置;检查是否有 CDN 缓存(Vercel 的 CDN 缓存可能覆盖 ISR);调用 revalidatePath 主动刷新。
- 自定义服务器后静态优化失效:自定义服务器不支持 Next.js 的自动静态优化(输出
.html 文件)。如果不需要自定义 HTTP 层,直接用 next start。
面试常问
-
Next.js 怎么部署到生产环境?
首选 Vercel:Git 关联后自动构建、CDN 分发、自动 HTTPS。备选 Docker:output: 'standalone' 构建最小镜像,用 next start 或自定义服务器运行,部署到 AWS/阿里云/K8s。
-
环境变量怎么管理?
开发用 .env.local(不提交 Git),生产用平台环境变量(Vercel Dashboard / Docker ENV)。客户端变量加 NEXT_PUBLIC_ 前缀,服务端变量不加。
-
ISR 的预热策略?
用定时任务(cron)或部署后脚本,对所有 ISR 页面发一次 GET 请求触发重新生成。Vercel 可用 Vercel Cron Jobs,Docker 可用系统的 cron 或 CI 定时触发。
-
自定义服务器什么时候用?
需要自定义 HTTP 层时:WebSocket、特殊代理、日志中间件、TLS 配置。不需要时直接用 next start,保留所有自动优化。
-
Vercel 的冷启动怎么解决?
① 升级到 Pro 方案选 Edge Runtime(无冷启动);② 用 ISR 减少 Function 调用;③ 预热页面和 API;④ 用 maxDuration 延长 Function 生命周期(Pro 支持)。
自测三问
NEXT_PUBLIC_API_URL 和 DATABASE_URL 在运行时分别出现在哪里?
- Docker 的
standalone 输出和默认输出有什么区别?
- ISR 页面为什么需要预热?不预热会怎样?
小结一句话
部署不是最后一步,而是交付的一环:Vercel 是零配置首选,Docker 是通用方案,环境变量分前后端,ISR 要预热,自定义服务器要慎重——按场景选对部署策略,代码才能稳定跑在生产环境。
延伸