mcpField
N
nextjs-deployment

nextjs-deployment

github.com/josiahsiegel云与 DevOps5979 次安装19 周前更新
代码仓库

关于 nextjs-deployment

nextjs-deployment 是帮助 Next.js 项目开发者规划生产部署的技能。它汇总 Vercel、Node.js、Docker 和静态导出的配置方式,并涵盖 Edge Runtime、环境变量、监控与 CI/CD。

主要功能

  1. 01部署到 Vercel、Node.js 或 Docker
  2. 02配置静态导出
  3. 03设置 Edge Runtime 路由
  4. 04管理部署环境变量
  5. 05配置健康检查与监控
  6. 06提供 GitHub Actions 部署示例

适用场景

  1. 01将 Next.js 应用部署到 Vercel
  2. 02使用 Docker 或 Node.js 自托管应用
  3. 03为 CDN 构建静态站点
  4. 04配置生产环境变量和部署流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill nextjs-deployment

安装后在 Claude Code 中输入/nextjs-deployment确认已加载。

详细介绍

快速参考

输出模式配置使用场景
默认-Vercel/Node.js 托管
独立output: 'standalone'Docker 容器
静态output: 'export'静态托管(无 SSR)
平台命令备注
Vercelvercel --prod自动部署,推荐
Node.jsnpm run build && npm start自托管
Dockerdocker build -t app .容器部署
静态npm run build → out/CDN 托管
运行时配置功能
Node.js默认完整功能
Edgeexport const runtime = 'edge'速度快,API 有限
环境文件用途
开发.env.local本地开发
生产.env.production构建时
运行时平台密钥服务端运行时

何时使用此技能

适用于部署和生产环境:

  • 部署到 Vercel(最简单的方式)
  • 使用 Docker 或 Node.js 自托管
  • 为 CDN 托管进行静态导出
  • 设置 CI/CD 流水线
  • 生产环境配置和监控

相关技能:

  • 关于中间件:请参阅 nextjs-middleware
  • 关于生产环境缓存:请参阅 nextjs-caching
  • 关于身份验证:请参阅 nextjs-authentication

Next.js 部署

Vercel 部署

自动部署
# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

# Production deployment
vercel --prod
环境变量
# Set environment variables
vercel env add DATABASE_URL production

# Pull env vars locally
vercel env pull .env.local
vercel.json 配置
{
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "framework": "nextjs",
  "regions": ["iad1", "sfo1"],
  "functions": {
    "app/api/**/*.ts": {
      "memory": 1024,
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" }
      ]
    }
  ],
  "redirects": [
    {
      "source": "/old-page",
      "destination": "/new-page",
      "permanent": true
    }
  ],
  "rewrites": [
    {
      "source": "/api/proxy/:path*",
      "destination": "https://api.example.com/:path*"
    }
  ]
}

自托管部署

Node.js 服务端
# Build the application
npm run build

# Start production server
npm run start
自定义服务端
// server.ts
import { createServer } from 'http';
import { parse } from 'url';
import next from 'next';

const dev = process.env.NODE_ENV !== 'production';
const hostname = process.env.HOSTNAME || 'localhost';
const port = parseInt(process.env.PORT || '3000', 10);

const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer(async (req, res) => {
    try {
      const parsedUrl = parse(req.url!, true);
      await handle(req, res, parsedUrl);
    } catch (err) {
      console.error('Error occurred handling', req.url, err);
      res.statusCode = 500;
      res.end('Internal Server Error');
    }
  })
    .once('error', (err) => {
      console.error(err);
      process.exit(1);
    })
    .listen(port, () => {
      console.log(`> Ready on http://${hostname}:${port}`);
    });
});
PM2 进程管理器
// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'nextjs-app',
      script: 'npm',
      args: 'start',
      instances: 'max',
      exec_mode: 'cluster',
      autorestart: true,
      watch: false,
      max_memory_restart: '1G',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
    },
  ],
};
# Start with PM2
pm2 start ecosystem.config.js

# Monitor
pm2 monit

# Logs
pm2 logs

Docker 部署

Dockerfile
# Dockerfile
FROM node:20-alpine AS base

# Dependencies stage
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci

# Builder stage
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build

# Runner stage
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

# Automatically leverage output traces
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.js
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
};

module.exports = nextConfig;
Docker Compose
# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
      - NEXTAUTH_SECRET=your-secret
      - NEXTAUTH_URL=http://localhost:3000
    depends_on:
      - db

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

volumes:
  postgres_data:

静态导出

配置
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  // Optional: Change the output directory
  distDir: 'dist',
  // Optional: Add trailing slashes
  trailingSlash: true,
  // Optional: Disable image optimization
  images: {
    unoptimized: true,
  },
};

module.exports = nextConfig;
构建静态导出
npm run build
# Output in 'out' directory (or distDir if configured)
限制
  • 不支持服务端渲染
  • 不支持 API 路由
  • 不支持中间件
  • 不支持 ISR
  • 图片优化需要外部服务

Edge 运行时

Edge 路由处理程序
// app/api/edge/route.ts
export const runtime = 'edge';

export async function GET(request: Request) {
  return new Response(JSON.stringify({ message: 'Hello from Edge!' }), {
    headers: { 'content-type': 'application/json' },
  });
}
Edge 中间件
// middleware.ts
// Runs on Edge by default
import { NextResponse } from 'next/server';

export function middleware(request: Request) {
  return NextResponse.next();
}
Edge Config(Vercel)
import { get } from '@vercel/edge-config';

export const runtime = 'edge';

export async function GET() {
  const greeting = await get('greeting');
  return Response.json({ greeting });
}

生产环境检查清单

环境变量
# .env.production
DATABASE_URL=
NEXTAUTH_SECRET=
NEXTAUTH_URL=https://myapp.com
NEXT_PUBLIC_API_URL=https://api.myapp.com
生产环境 next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Strict mode for development
  reactStrictMode: true,

  // Optimize images
  images: {
    domains: ['images.example.com'],
    formats: ['image/avif', 'image/webp'],
  },

  // Security headers
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          { key: 'X-Frame-Options', value: 'DENY' },
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        ],
      },
    ];
  },

  // Redirects
  async redirects() {
    return [
      {
        source: '/old-blog/:slug',
        destination: '/blog/:slug',
        permanent: true,
      },
    ];
  },

  // Experimental features
  experimental: {
    // Enable PPR
    ppr: true,
  },
};

module.exports = nextConfig;
健康检查端点
// app/api/health/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  try {
    // Check database connection
    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 }
    );
  }
}

监控

错误跟踪(Sentry)
// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');

const nextConfig = {
  // Your config
};

module.exports = withSentryConfig(nextConfig, {
  silent: true,
  org: 'your-org',
  project: 'your-project',
});
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
});
分析
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}

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

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

      - 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'

最佳实践

实践说明
使用 output: 'standalone'缩小 Docker 镜像
设置正确的环境变量使用 Vercel/平台密钥
启用缓存使用 CDN、浏览器缓存
监控性能Vercel Analytics、Sentry
使用健康检查与负载均衡器集成
启用压缩在反向代理中使用 Gzip/Brotli

评论

支持 Markdown