Skip to content

部署方案

🙋 应用开发完成,如何部署到生产环境?不同场景需要不同的部署策略。

部署方式对比#

方式优点缺点适用场景
Vercel零配置、自动优化成本较高快速上线、小团队
Docker可移植、环境一致需要运维企业内部、自建服务
静态导出简单、CDN 友好功能受限纯静态站点
Node.js完整功能需要服务器自托管

Vercel 部署#

连接仓库#

Terminal window
# 安装 Vercel CLI
pnpm add -g vercel
# 登录
vercel login
# 部署
vercel

环境变量#

在 Vercel Dashboard 中配置:

Settings > Environment Variables

或使用 CLI:

Terminal window
vercel env add DATABASE_URL production
vercel env add API_SECRET production

vercel.json 配置#

{
"buildCommand": "pnpm build",
"outputDirectory": ".next",
"framework": "nextjs",
"regions": ["hkg1"],
"headers": [
{
"source": "/api/(.*)",
"headers": [{ "key": "Access-Control-Allow-Origin", "value": "*" }]
}
],
"redirects": [
{
"source": "/old-path",
"destination": "/new-path",
"permanent": true
}
]
}

自定义域名#

Settings > Domains > Add Domain

输入域名后,按照提示配置 DNS:

Type: CNAME
Name: www
Value: cname.vercel-dns.com
Type: A
Name: @
Value: 76.76.21.21

Docker 部署#

Dockerfile#

# Dockerfile
FROM node:20-alpine AS base
# 安装依赖
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# 构建
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build
# 生产镜像
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]

next.config.ts 配置#

next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
output: 'standalone', // 生成独立构建
}
export default config

构建和运行#

Terminal window
# 构建镜像
docker build -t my-nextjs-app .
# 运行容器
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-e API_SECRET="..." \
my-nextjs-app

Docker Compose#

docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- '3000:3000'
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
Terminal window
# 启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f app
# 停止服务
docker-compose down

自托管 Node.js#

构建#

Terminal window
pnpm build

启动服务器#

Terminal window
# 开发
pnpm start
# 生产(使用 PM2)
pm2 start npm --name "nextjs" -- start

PM2 配置#

ecosystem.config.js
module.exports = {
apps: [
{
name: 'nextjs',
script: 'node_modules/.bin/next',
args: 'start',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
},
],
}
Terminal window
pm2 start ecosystem.config.js
pm2 save
pm2 startup

Nginx 反向代理#

/etc/nginx/sites-available/nextjs
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# 静态文件缓存
location /_next/static {
proxy_pass http://localhost:3000;
proxy_cache_valid 60m;
add_header Cache-Control "public, max-age=31536000, immutable";
}
location /static {
proxy_pass http://localhost:3000;
proxy_cache_valid 60m;
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
Terminal window
# 启用配置
sudo ln -s /etc/nginx/sites-available/nextjs /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL 证书(Let’s Encrypt)#

Terminal window
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

静态导出#

配置#

next.config.ts
const config: NextConfig = {
output: 'export',
images: {
unoptimized: true, // 静态导出需要禁用图片优化
},
}

构建#

Terminal window
pnpm build
# 输出到 out/ 目录

部署到 CDN#

Terminal window
# Cloudflare Pages
npx wrangler pages deploy out
# GitHub Pages
# 将 out/ 目录推送到 gh-pages 分支
# Netlify
netlify deploy --prod --dir=out

限制#

静态导出不支持:

CI/CD 配置#

GitHub Actions#

.github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test:run
- name: Build
run: pnpm build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'

Docker 构建和推送#

.github/workflows/docker.yml
name: Docker Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
username/myapp:latest
username/myapp:${{ github.sha }}

监控与日志#

健康检查#

app/api/health/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
try {
// 检查数据库连接
await db.$queryRaw`SELECT 1`
return NextResponse.json({
status: 'healthy',
timestamp: new Date().toISOString(),
})
} catch (error) {
return NextResponse.json(
{ status: 'unhealthy', error: 'Database connection failed' },
{ status: 503 }
)
}
}

日志收集#

lib/logger.ts
import pino from 'pino'
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport:
process.env.NODE_ENV === 'development'
? { target: 'pino-pretty' }
: undefined,
})
middleware.ts
import { logger } from '@/lib/logger'
export function middleware(request: NextRequest) {
logger.info({
method: request.method,
url: request.url,
userAgent: request.headers.get('user-agent'),
})
return NextResponse.next()
}

性能优化#

CDN 配置#

next.config.ts
const config: NextConfig = {
// 静态资源使用 CDN
assetPrefix: process.env.CDN_URL,
// 图片优化器使用 CDN
images: {
loader: 'custom',
loaderFile: './lib/image-loader.ts',
},
}

缓存策略#

next.config.ts
const config: NextConfig = {
headers: async () => [
{
source: '/:all*(svg|jpg|png|webp)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
],
}

常见问题#

🤔 Q: Vercel 免费版有什么限制?

🤔 Q: Docker 构建很慢怎么办?

使用多阶段构建和缓存:

# 使用 BuildKit 缓存
RUN --mount=type=cache,target=/root/.pnpm-store \
pnpm install --frozen-lockfile

🤔 Q: 如何实现零停机部署?

使用蓝绿部署或滚动更新:

docker-compose.yml
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s

恭喜!下一篇将进入项目实战,综合运用所学知识构建完整应用。

-EOF-