1. 环境准备
1.1 生产环境配置
首先创建生产环境配置文件:
// src/config/production.ts
export const productionConfig = {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
databaseUrl: process.env.DATABASE_URL,
openaiApiKey: process.env.OPENAI_API_KEY,
nextAuthSecret: process.env.NEXTAUTH_SECRET,
nextAuthUrl: process.env.NEXTAUTH_URL,
}
1.2 环境变量设置
创建 .env.production 文件:
# .env.production
NEXT_PUBLIC_API_URL=https://your-domain.com/api
DATABASE_URL="mysql://username:password@host:3306/mindai"
OPENAI_API_KEY=your-openai-api-key
NEXTAUTH_SECRET=your-nextauth-secret
NEXTAUTH_URL=https://your-domain.com
2. 构建优化
2.1 Next.js 构建配置
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
images: {
domains: ['localhost', 'your-domain.com'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
experimental: {
serverActions: true,
serverComponentsExternalPackages: ['@prisma/client'],
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
fs: false,
net: false,
tls: false,
}
}
return config
},
}
module.exports = nextConfig
2.2 性能优化
// src/middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 添加安全相关的响应头
const response = NextResponse.next()
response.headers.set('X-DNS-Prefetch-Control', 'on')
response.headers.set(
'Strict-Transport-Security',
'max-age=63072000; includeSubDomains; preload'
)
response.headers.set('X-Frame-Options', 'SAMEORIGIN')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('Referrer-Policy', 'origin-when-cross-origin')
// 设置缓存策略
if (request.nextUrl.pathname.startsWith('/api/')) {
response.headers.set('Cache-Control', 'no-store')
} else if (
request.nextUrl.pathname.match(/\.(js|css|svg|png|jpg|jpeg|gif|ico)$/)
) {
response.headers.set(
'Cache-Control',
'public, max-age=31536000, immutable'
)
}
return response
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico).*)',
],
}
3. Docker部署
3.1 Dockerfile
# Dockerfile
FROM node:18-alpine AS base
# 依赖阶段
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# 构建阶段
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# 生产阶段
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
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"]
3.2 Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- NEXTAUTH_URL=${NEXTAUTH_URL}
depends_on:
- db
restart: always
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=mindai
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
volumes:
mysql_data:
3.3 Nginx配置
# nginx.conf
events {
worker_connections 1024;
}
http {
upstream nextjs_upstream {
server app:3000;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
client_max_body_size 20M;
location / {
proxy_pass http://nextjs_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
location /_next/static {
proxy_cache STATIC;
proxy_pass http://nextjs_upstream;
add_header Cache-Control "public, max-age=31536000, immutable";
}
location /static {
proxy_cache STATIC;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 60m;
proxy_pass http://nextjs_upstream;
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
}
4. 自动化部署
4.1 GitHub Actions配置
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
push: true
tags: your-username/mindai:latest
cache-from: type=registry,ref=your-username/mindai:buildcache
cache-to: type=registry,ref=your-username/mindai:buildcache,mode=max
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /path/to/project
docker-compose pull
docker-compose up -d
5. 监控与日志
5.1 日志配置
// src/lib/logger.ts
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
})
export default logger
5.2 性能监控
// src/lib/monitoring.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function monitoringMiddleware(request: NextRequest) {
const start = Date.now()
const response = NextResponse.next()
response.headers.set('Server-Timing', `total;dur=${Date.now() - start}`)
// 记录请求信息
logger.info({
method: request.method,
url: request.url,
duration: Date.now() - start,
status: response.status,
})
return response
}
6. 备份策略
6.1 数据库备份脚本
#!/bin/bash
# backup.sh
# 设置变量
BACKUP_DIR="/path/to/backups"
MYSQL_USER="root"
MYSQL_PASSWORD="your-password"
DATABASE="mindai"
DATE=$(date +%Y%m%d_%H%M%S)
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 执行备份
mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" "$DATABASE" > "$BACKUP_DIR/$DATABASE_$DATE.sql"
# 压缩备份文件
gzip "$BACKUP_DIR/$DATABASE_$DATE.sql"
# 删除30天前的备份
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
6.2 自动备份配置
# 添加到crontab
0 2 * * * /path/to/backup.sh
7. 部署检查清单
1. 环境变量检查
- 确认所有必要的环境变量已设置
- 验证数据库连接字符串
- 检查API密钥配置
- 安全检查
- SSL证书是否有效
- 敏感信息是否加密
- 防火墙规则是否正确
- 性能检查
- 静态资源是否正确缓存
- 数据库索引是否优化
- CDN是否正常工作
- 监控检查
- 日志系统是否正常运行
- 监控告警是否配置
- 备份系统是否正常
- 回滚计划
- 准备回滚脚本
- 确保有数据备份
- 测试回滚流程