mcpField
N
nextjs-deployment

nextjs-deployment

github.com/josiahsiegelCloud & DevOps5979 installsUpdated 19w ago
Repository

About nextjs-deployment

nextjs-deployment is a skill for developers preparing Next.js projects for production. It covers configuration for Vercel, Node.js, Docker, and static export, along with Edge Runtime, environment variables, monitoring, and CI/CD.

Key features

  1. 01Deploy to Vercel, Node.js, or Docker
  2. 02Configure static export
  3. 03Set up Edge Runtime routes
  4. 04Manage deployment environment variables
  5. 05Configure health checks and monitoring
  6. 06Provide a GitHub Actions deployment example

Use cases

  1. 01Deploy a Next.js app to Vercel
  2. 02Self-host an app with Docker or Node.js
  3. 03Build a static site for CDN hosting
  4. 04Set up production variables and deployment workflows

Install

Copy the command and run it in a terminal.

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

After installing, type/nextjs-deploymentin Claude Code to confirm it loaded.

Details

From the project README

Quick Reference

Output ModeConfigUse Case
Default-Vercel/Node.js hosting
Standaloneoutput: 'standalone'Docker containers
Staticoutput: 'export'Static hosting (no SSR)
PlatformCommandNotes
Vercelvercel --prodAutomatic, recommended
Node.jsnpm run build && npm startSelf-hosted
Dockerdocker build -t app .Container deployment
Staticnpm run build → out/CDN hosting
RuntimeConfigFeatures
Node.jsDefaultFull features
Edgeexport const runtime = 'edge'Fast, limited APIs
EnvironmentFileUsage
Development.env.localLocal dev
Production.env.productionBuild time
RuntimePlatform secretsServer runtime

When to Use This Skill

Use for deployment and production:

  • Deploying to Vercel (easiest path)
  • Self-hosting with Docker or Node.js
  • Static export for CDN hosting
  • Setting up CI/CD pipelines
  • Production configuration and monitoring

Related skills:

  • For middleware: see nextjs-middleware
  • For caching in production: see nextjs-caching
  • For authentication: see nextjs-authentication

Next.js Deployment

Vercel Deployment

Automatic Deployment
# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

# Production deployment
vercel --prod
Environment Variables
# Set environment variables
vercel env add DATABASE_URL production

# Pull env vars locally
vercel env pull .env.local
vercel.json Configuration
{
  "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*"
    }
  ]
}

Self-Hosted Deployment

Node.js Server
# Build the application
npm run build

# Start production server
npm run start
Custom Server
// 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 Process Manager
// 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 Deployment

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 for Standalone
// 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:

Static Export

Configuration
// 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;
Build Static Export
npm run build
# Output in 'out' directory (or distDir if configured)
Limitations
  • No Server-Side Rendering
  • No API Routes
  • No Middleware
  • No ISR
  • Image Optimization requires external service

Edge Runtime

Edge Route Handler
// 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
// 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 });
}

Production Checklist

Environment Variables
# .env.production
DATABASE_URL=
NEXTAUTH_SECRET=
NEXTAUTH_URL=https://myapp.com
NEXT_PUBLIC_API_URL=https://api.myapp.com
next.config.js Production
/** @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;
Health Check Endpoint
// 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 }
    );
  }
}

Monitoring

Error Tracking (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,
});
Analytics
// 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'

Best Practices

PracticeDescription
Use output: 'standalone'Smaller Docker images
Set proper env varsUse Vercel/platform secrets
Enable cachingUse CDN, browser caching
Monitor performanceVercel Analytics, Sentry
Use health checksLoad balancer integration
Enable compressionGzip/Brotli in reverse proxy

Comments

Markdown supported