Nuxt: 项目部署
最后更新:2026-08-26
MegaShop 开发完成,Charlie 要把它部署到生产环境。这不是简单的 npm run build——需要 Docker 编排全套服务、CI/CD 自动化流水线、运行时监控和错误追踪、完整的上线检查清单。一次部署,长期稳定运行。
1. 你将学到
- 生产构建优化:nuxt build 分析 + bundle 优化 + 环境变量安全
- Docker Compose 编排:Nuxt + PostgreSQL + Redis + Nginx
- CI/CD 流水线:GitHub Actions 自动测试 + 多环境部署
- 监控与日志:Sentry 错误追踪 + API 日志 + 性能指标
- 上线检查清单:SSL/CDN/迁移/缓存预热/回滚预案
2. 一个架构师的真实故事
(1) 痛点:上线即崩溃
Charlie 第一次部署 MegaShop——忘记跑数据库迁移、环境变量漏配、Redis 没启动、SSL 证书过期。上线 5 分钟后 Alice 就发现 500 错误,Bob 手动回滚花了 1 小时。
(2) 生产级部署体系的解法
完整的部署体系:自动化 CI/CD + Docker Compose + 监控 + 上线检查清单,每一步都有验证,出错自动回滚。
(3) 收益:自动化 + 可观测
推送代码后全自动部署,Sentry 实时捕获错误,Prometheus 监控性能指标,Alice 再也不会遇到未发现的 500 错误。
3. 生产构建优化
(1) 构建产物分析
▶ 示例:Build 分析与优化
BASH
# Build for production
npm run build
# Analyze bundle
npx nuxi analyze
输出:
TEXT
📖 仅展示
# 命令执行成功
▶ 示例:环境变量安全检查
TYPESCRIPT
// server/utils/env-check.ts
export default defineEventHandler((event) => {
if (getRequestURL(event).pathname !== '/api/health') return
const config = useRuntimeConfig()
const required = ['databaseUrl', 'jwtAccessSecret', 'jwtRefreshSecret']
const missing = required.filter(key => !config[key as keyof typeof config])
if (missing.length > 0 && process.env.NODE_ENV === 'production') {
console.error(`Missing required env vars: ${missing.join(', ')}`)
}
return {
status: missing.length === 0 ? 'ok' : 'degraded',
timestamp: Date.now(),
version: process.env.APP_VERSION || 'unknown'
}
})
输出:
TEXT
📖 仅展示
// 执行成功
(2) 构建优化检查清单
| 检查项 | 要求 | 验证方法 |
|---|---|---|
| JS Bundle gzip | < 200KB | nuxi analyze |
| CSS 提取 | ✅ 独立文件 | 构建产物检查 |
| 图片 WebP | ✅ 自动转换 | NuxtImg 验证 |
| 环境变量 | 无硬编码密钥 | grep 检查 |
| Tree Shaking | 无未使用代码 | bundle 分析 |
| Source Map | 生产关闭 | nuxt.config.ts |
4. Docker Compose 编排
(1) 生产部署流程
flowchart TB
A[Git Push] --> B[GitHub Actions CI]
B --> C{Tests Pass?}
C -->|No| D[Notify Team + Block]
C -->|Yes| E[Build Docker Image]
E --> F[Push to Registry]
F --> G[Deploy to Staging]
G --> H{Smoke Test?}
H -->|No| I[Rollback Staging]
H -->|Yes| J[Deploy to Production]
J --> K{Health Check?}
K -->|No| L[Auto Rollback]
K -->|Yes| M[Done ✅]
▶ 示例:生产级 Docker Compose
YAML
# docker-compose.prod.yml
version: '3.8'
services:
web:
image: ghcr.io/megashop/megashop:latest
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://megashop:${DB_PASSWORD}@db:5432/megashop
- REDIS_URL=redis://redis:6379
- JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
- DEPLOY_TARGET=node-server
depends_on:
db: { condition: service_healthy }
redis: { condition: service_started }
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
deploy:
resources:
limits: { memory: 1G, cpus: '1.0' }
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=megashop
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=megashop
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U megashop"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.prod.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- web
restart: unless-stopped
volumes:
postgres_data:
redis_data:
输出:
TEXT
📖 仅展示
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
5. CI/CD 流水线
▶ 示例:完整的生产部署工作流
YAML
# .github/workflows/deploy-production.yml
name: Deploy Production
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: megashop/megashop
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env: { POSTGRES_USER: test, POSTGRES_PASSWORD: test, POSTGRES_DB: megashop_test }
ports: ['5432:5432']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npx prisma migrate deploy
env: { DATABASE_URL: postgresql://test:test@localhost:5432/megashop_test }
- run: npm run lint
- run: npm run typecheck
- run: npm run test:coverage
build-push:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
deploy:
needs: build-push
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /opt/megashop
docker pull ghcr.io/megashop/megashop:latest
docker compose -f docker-compose.prod.yml up -d --no-build
docker compose exec web npx prisma migrate deploy
echo "Deploy completed at $(date)"
- name: Health check
run: |
for i in {1..15}; do
if curl -sf https://megashop.com/api/health; then
echo "Health check passed"
exit 0
fi
echo "Waiting for health check... ($i/15)"
sleep 5
done
echo "Health check failed!"
exit 1
- name: Rollback on failure
if: failure()
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /opt/megashop
docker compose down
docker tag ghcr.io/megashop/megashop:previous ghcr.io/megashop/megashop:latest
docker compose -f docker-compose.prod.yml up -d --no-build
echo "Rollback completed at $(date)"
输出:
TEXT
📖 仅展示
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
6. 监控与日志
▶ 示例:Sentry 错误追踪
TYPESCRIPT
// plugins/sentry.client.ts
import * as Sentry from '@sentry/vue'
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig()
Sentry.init({
dsn: config.public.sentryDsn,
environment: config.public.environment,
release: config.public.version,
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(nuxtApp.$router)
})
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0
})
return {
provide: {
sentry: Sentry
}
}
})
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:Server 端日志
TYPESCRIPT
// server/middleware/logging.ts
export default defineEventHandler((event) => {
const start = Date.now()
const method = getMethod(event)
const url = getRequestURL(event)
event.node.res.on('finish', () => {
const duration = Date.now() - start
const status = event.node.res.statusCode
// Structured log
const logEntry = {
method, url: url.pathname,
status, duration,
userId: event.context.user?.id || null,
timestamp: new Date().toISOString()
}
if (status >= 500) {
console.error(JSON.stringify(logEntry))
} else if (duration > 1000) {
console.warn(JSON.stringify({ ...logEntry, alert: 'slow_request' }))
} else {
console.log(JSON.stringify(logEntry))
}
})
})
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:健康检查 API
TYPESCRIPT
// server/api/health.get.ts
export default defineEventHandler(async () => {
const checks: Record<string, string> = {}
// Check database
try {
await prisma.$queryRaw`SELECT 1`
checks.database = 'ok'
} catch {
checks.database = 'error'
}
// Check Redis
try {
const storage = useStorage('products')
await storage.setItem('health-check', 'ok', { ttl: 10 })
checks.redis = 'ok'
} catch {
checks.redis = 'error'
}
const allOk = Object.values(checks).every(v => v === 'ok')
setHeader(useEvent(), 'cache-control', 'no-store')
return {
status: allOk ? 'ok' : 'degraded',
checks,
version: process.env.APP_VERSION || 'unknown',
uptime: process.uptime(),
timestamp: new Date().toISOString()
}
})
输出:
TEXT
📖 仅展示
// 执行成功
7. 上线检查清单
(1) 生产上线检查
| # | 检查项 | 操作 | 验证方法 |
|---|---|---|---|
| 1 | SSL 证书 | 配置 Let's Encrypt | curl -v https://megashop.com |
| 2 | 环境变量 | 所有密钥已配置 | /api/health 检查 |
| 3 | 数据库迁移 | prisma migrate deploy | 查询商品验证 |
| 4 | Redis 连接 | 启动并连接 | /api/health 检查 |
| 5 | CDN 配置 | Cloudflare/CloudFront | 静态资源缓存命中 |
| 6 | 缓存预热 | 热门商品页首次访问 | curl 首页 + 列表页 |
| 7 | 健康检查 | /api/health 返回 ok | 自动化验证 |
| 8 | Sentry 初始化 | 错误追踪可用 | 触发测试错误 |
| 9 | Sitemap 生成 | /sitemap.xml 可访问 | Google Search Console |
| 10 | 回滚预案 | 旧镜像可用 | 记录 rollback 步骤 |
(2) 回滚预案
| 步骤 | 操作 | 命令 |
|---|---|---|
| 1 | 停止当前服务 | docker compose down |
| 2 | 恢复旧镜像 | docker tag xxx:previous xxx:latest |
| 3 | 启动旧版本 | docker compose up -d --no-build |
| 4 | 验证健康 | curl /api/health |
| 5 | 通知团队 | Slack/Email 通知 |
8. 综合示例:MegaShop 部署命令
BASH
# ============================================
# MegaShop Production Deployment
# Complete deployment + monitoring setup
# ============================================
# 1. Set up server (first time only)
ssh prod-server
mkdir -p /opt/megashop/ssl
# Copy SSL certificates
# Copy .env with production secrets
# 2. Initial deployment
cd /opt/megashop
git clone https://github.com/megashop/megashop.git .
docker compose -f docker-compose.prod.yml up -d
# 3. Database setup
docker compose exec web npx prisma migrate deploy
docker compose exec web npx prisma db seed
# 4. Verify
curl -f https://megashop.com/api/health
curl -f https://megashop.com/sitemap.xml
# 5. Cache warmup
curl https://megashop.com/
curl https://megashop.com/products
curl https://megashop.com/products/1
# 6. Monitor
docker compose logs -f web
# Check Sentry dashboard
# Check health endpoint periodically
❓ 常见问题
Q Docker 镜像怎么更新?
A CI/CD 自动 build + push 新镜像到 GHCR,服务器 docker pull + docker compose up -d --no-build。零停机用蓝绿部署。
Q Sentry 免费额度够用吗?
A 免费版每月 5 thousand 错误事件。MegaShop 初期足够,后期按需升级。开发错误不计入(只在生产上报)。
Q 健康检查接口需要鉴权吗?
A 不需要。/api/health 必须公开,用于负载均衡器、K8s、Docker healthcheck 探测。但不要暴露敏感信息。
Q 缓存预热怎么做?
A 部署后用脚本访问热门页面(首页/列表页/Top 100 商品详情),触发 ISR 缓存生成。也可在 seed 后自动执行。
Q 日志怎么收集?
A Docker 日志用 docker compose logs。生产环境推荐 ELK(Elasticsearch + Logstash + Kibana)或 Loki + Grafana。MegaShop 初期用结构化 console.log + Sentry。
Q 数据库迁移失败怎么办?
A Prisma migrate deploy 只应用已验证的迁移文件。如果迁移失败,修复迁移文件后重新部署。生产永远不用 prisma migrate reset(会丢数据)。
📖 小节
- 生产构建:bundle 分析 + 环境变量安全 + Source Map 关闭
- Docker Compose 编排:Nuxt + PostgreSQL + Redis + Nginx 全套服务
- CI/CD:GitHub Actions 自动测试 + 构建 + 部署 + 健康检查 + 自动回滚
- 监控:Sentry 错误追踪 + 结构化日志 + 健康检查 API
- 上线检查清单 10 项:SSL/环境变量/迁移/Redis/CDN/缓存预热/回滚预案
📝 作业
- 基础题(难度⭐):将 MegaShop 部署到生产环境(Docker Compose),验证 /api/health 返回 ok
- 进阶题(难度⭐⭐):配置 GitHub Actions CI/CD,实现 push main 自动部署,包含健康检查和自动回滚
- 挑战题(难度⭐⭐⭐):完成完整的运维体系——Sentry 错误追踪 + 结构化日志 + Prometheus 指标 + 上线检查清单自动化验证
---|