🙋 应用开发完成,如何部署到生产环境?不同场景需要不同的部署策略。
部署方式对比#
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Vercel | 零配置、自动优化 | 成本较高 | 快速上线、小团队 |
| Docker | 可移植、环境一致 | 需要运维 | 企业内部、自建服务 |
| 静态导出 | 简单、CDN 友好 | 功能受限 | 纯静态站点 |
| Node.js | 完整功能 | 需要服务器 | 自托管 |
Vercel 部署#
连接仓库#
# 安装 Vercel CLIpnpm add -g vercel
# 登录vercel login
# 部署vercel环境变量#
在 Vercel Dashboard 中配置:
Settings > Environment Variables或使用 CLI:
vercel env add DATABASE_URL productionvercel env add API_SECRET productionvercel.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: CNAMEName: wwwValue: cname.vercel-dns.com
Type: AName: @Value: 76.76.21.21Docker 部署#
Dockerfile#
# DockerfileFROM node:20-alpine AS base
# 安装依赖FROM base AS depsRUN apk add --no-cache libc6-compatWORKDIR /app
COPY package.json pnpm-lock.yaml ./RUN corepack enable pnpm && pnpm install --frozen-lockfile
# 构建FROM base AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build
# 生产镜像FROM base AS runnerWORKDIR /app
ENV NODE_ENV productionENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./publicCOPY --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 3000ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]next.config.ts 配置#
import type { NextConfig } from 'next'
const config: NextConfig = { output: 'standalone', // 生成独立构建}
export default config构建和运行#
# 构建镜像docker build -t my-nextjs-app .
# 运行容器docker run -p 3000:3000 \ -e DATABASE_URL="postgresql://..." \ -e API_SECRET="..." \ my-nextjs-appDocker Compose#
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:# 启动所有服务docker-compose up -d
# 查看日志docker-compose logs -f app
# 停止服务docker-compose down自托管 Node.js#
构建#
pnpm build启动服务器#
# 开发pnpm start
# 生产(使用 PM2)pm2 start npm --name "nextjs" -- startPM2 配置#
module.exports = { apps: [ { name: 'nextjs', script: 'node_modules/.bin/next', args: 'start', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, }, }, ],}pm2 start ecosystem.config.jspm2 savepm2 startupNginx 反向代理#
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"; }}# 启用配置sudo ln -s /etc/nginx/sites-available/nextjs /etc/nginx/sites-enabled/sudo nginx -tsudo systemctl reload nginxSSL 证书(Let’s Encrypt)#
sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d example.com -d www.example.com静态导出#
配置#
const config: NextConfig = { output: 'export', images: { unoptimized: true, // 静态导出需要禁用图片优化 },}构建#
pnpm build# 输出到 out/ 目录部署到 CDN#
# Cloudflare Pagesnpx wrangler pages deploy out
# GitHub Pages# 将 out/ 目录推送到 gh-pages 分支
# Netlifynetlify deploy --prod --dir=out限制#
静态导出不支持:
- 动态路由(未使用
generateStaticParams) - API Routes
- 中间件
- ISR
headers、redirects、rewrites配置
CI/CD 配置#
GitHub Actions#
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 构建和推送#
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 }}监控与日志#
健康检查#
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 } ) }}日志收集#
import pino from 'pino'
export const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: process.env.NODE_ENV === 'development' ? { target: 'pino-pretty' } : undefined,})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 配置#
const config: NextConfig = { // 静态资源使用 CDN assetPrefix: process.env.CDN_URL,
// 图片优化器使用 CDN images: { loader: 'custom', loaderFile: './lib/image-loader.ts', },}缓存策略#
const config: NextConfig = { headers: async () => [ { source: '/:all*(svg|jpg|png|webp)', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], }, ],}常见问题#
🤔 Q: Vercel 免费版有什么限制?
- 带宽:100GB/月
- 执行时间:10秒(Serverless Functions)
- 构建时间:6000分钟/月
🤔 Q: Docker 构建很慢怎么办?
使用多阶段构建和缓存:
# 使用 BuildKit 缓存RUN --mount=type=cache,target=/root/.pnpm-store \ pnpm install --frozen-lockfile🤔 Q: 如何实现零停机部署?
使用蓝绿部署或滚动更新:
deploy: replicas: 3 update_config: parallelism: 1 delay: 10s恭喜!下一篇将进入项目实战,综合运用所学知识构建完整应用。
-EOF-