Next.js 应用如何部署?需要哪些准备工作?output: 'standalone' 选项有什么作用?

218 阅读4分钟

Next.js 应用如何部署?需要哪些准备工作?output: 'standalone' 选项有什么作用?

部署准备工作

1. 构建优化

// next.config.js
const nextConfig = {
  // 启用 standalone 输出
  output: 'standalone',

  // 优化构建
  swcMinify: true,
  compress: true,

  // 图片优化
  images: {
    unoptimized: false,
    domains: ['example.com'],
  },

  // 实验性功能
  experimental: {
    outputFileTracingRoot: path.join(__dirname, '../../'),
  },
}

module.exports = nextConfig

2. 环境变量配置

# .env.production
NODE_ENV=production
DATABASE_URL=postgresql://user:password@prod-db:5432/mydb
JWT_SECRET=your-production-secret
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My App

3. 依赖优化

// package.json
{
  "dependencies": {
    "next": "^15.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/react": "^18.0.0",
    "typescript": "^5.0.0"
  },
  "scripts": {
    "build": "next build",
    "start": "next start",
    "dev": "next dev"
  }
}

output: 'standalone' 选项

1. 基本概念

// next.config.js
const nextConfig = {
  output: 'standalone',
}

module.exports = nextConfig

output: 'standalone' 选项会创建一个独立的部署包,包含:

  • 所有必需的依赖
  • 优化的服务器代码
  • 静态资源
  • 运行时配置

2. 构建输出结构

# 构建后的 .next/standalone 目录结构
.next/standalone/
├── server.js              # 服务器入口文件
├── package.json           # 运行时依赖
├── node_modules/          # 必需的依赖
├── .next/                 # 构建输出
│   ├── static/           # 静态资源
│   ├── server/           # 服务器代码
│   └── cache/            # 缓存文件
└── public/               # 公共资源

3. 独立部署

# 构建应用
npm run build

# 启动独立服务器
cd .next/standalone
node server.js

部署方式

1. Vercel 部署

# 安装 Vercel CLI
npm i -g vercel

# 部署到 Vercel
vercel

# 生产环境部署
vercel --prod
// vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "framework": "nextjs",
  "functions": {
    "app/api/**/*.js": {
      "runtime": "nodejs18.x"
    }
  }
}

2. Docker 部署

# Dockerfile
FROM node:18-alpine AS base

# 安装依赖
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 构建应用
FROM base AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
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

CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

3. 传统服务器部署

# 构建应用
npm run build

# 复制文件到服务器
scp -r .next/standalone user@server:/var/www/app
scp -r public user@server:/var/www/app

# 在服务器上启动
cd /var/www/app
node server.js
# 使用 PM2 管理进程
npm install -g pm2

# 启动应用
pm2 start server.js --name "nextjs-app"

# 设置开机自启
pm2 startup
pm2 save

4. 云平台部署

AWS Lambda
// next.config.js
const nextConfig = {
  output: 'export',
  trailingSlash: true,
  images: {
    unoptimized: true,
  },
}

module.exports = nextConfig
# serverless.yml
service: nextjs-app

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1

functions:
  app:
    handler: server.handler
    events:
      - http:
          path: /{proxy+}
          method: ANY
      - http:
          path: /
          method: ANY

plugins:
  - serverless-nextjs-plugin
Google Cloud Run
# Dockerfile
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

EXPOSE 3000
CMD ["npm", "start"]
# 部署到 Cloud Run
gcloud run deploy nextjs-app \
  --source . \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated

性能优化

1. 构建优化

// next.config.js
const nextConfig = {
  output: 'standalone',

  // 压缩优化
  compress: true,
  swcMinify: true,

  // 图片优化
  images: {
    formats: ['image/webp', 'image/avif'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },

  // 实验性功能
  experimental: {
    optimizeCss: true,
    optimizePackageImports: ['@mui/material', 'lodash'],
  },
}

2. 缓存策略

// next.config.js
const nextConfig = {
  async headers() {
    return [
      {
        source: '/static/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        source: '/api/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'no-cache, no-store, must-revalidate',
          },
        ],
      },
    ]
  },
}

3. 监控和日志

// lib/monitoring.js
export function setupMonitoring() {
  if (process.env.NODE_ENV === 'production') {
    // 设置错误监控
    process.on('uncaughtException', (error) => {
      console.error('Uncaught Exception:', error)
      // 发送到监控服务
    })

    process.on('unhandledRejection', (reason, promise) => {
      console.error('Unhandled Rejection:', reason)
      // 发送到监控服务
    })
  }
}

最佳实践

1. 环境配置

// lib/env.js
export const env = {
  NODE_ENV: process.env.NODE_ENV,
  PORT: process.env.PORT || 3000,
  DATABASE_URL: process.env.DATABASE_URL,
  JWT_SECRET: process.env.JWT_SECRET,
  API_URL: process.env.NEXT_PUBLIC_API_URL,
}

// 验证必需的环境变量
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET']

for (const envVar of requiredEnvVars) {
  if (!env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`)
  }
}

2. 健康检查

// app/api/health/route.js
export async function GET() {
  try {
    // 检查数据库连接
    await db.$queryRaw`SELECT 1`

    return NextResponse.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      version: process.env.NEXT_PUBLIC_VERSION,
    })
  } catch (error) {
    return NextResponse.json(
      {
        status: 'unhealthy',
        error: error.message,
      },
      { status: 500 }
    )
  }
}

3. 错误处理

// app/error.js
'use client'

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

总结

Next.js 部署要点:

准备工作

  • 构建优化配置
  • 环境变量设置
  • 依赖管理

output: 'standalone'

  • 创建独立部署包
  • 包含所有必需依赖
  • 简化部署流程

部署方式

  • Vercel (推荐)
  • Docker 容器化
  • 传统服务器
  • 云平台 (AWS, GCP)

最佳实践

  • 性能优化
  • 缓存策略
  • 监控和日志
  • 健康检查
  • 错误处理