kb.erickguedes.com
Next.js: React Full-Stack

Deploy em Produção

Aula 6 de 6

Deploy

Vercel (Plataforma Nativa)

# Deploy via CLI
npm install -g vercel
vercel
vercel --prod

# Configuração
# vercel.json
{
  "framework": "nextjs",
  "regions": ["gru1"],  // São Paulo
  "env": {
    "DATABASE_URL": "@database-url"
  }
}
# Variáveis de ambiente na Vercel
vercel env add DATABASE_URL production
vercel env add NEXT_PUBLIC_API_URL production

Docker (Self-hosted)

# Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine 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
services:
  nextjs:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - NEXTAUTH_URL=${NEXTAUTH_URL}
    depends_on:
      - postgres

  postgres:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}

volumes:
  pgdata:
// next.config.ts — Standalone output
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone', // Necessário para Docker
};

export default nextConfig;

Node.js Server

npm run build
npm start
// server.js (custom server — não recomendado a menos que necessário)
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer((req, res) => {
    const parsedUrl = parse(req.url!, true);
    handle(req, res, parsedUrl);
  }).listen(3000, () => {
    console.log('> Ready on http://localhost:3000');
  });
});

Static Export

// next.config.ts
const nextConfig = {
  output: 'export',
  images: {
    unoptimized: true // Necessário para export estático
  }
};

export default nextConfig;
npm run build
# Gera a pasta out/
# Pode servir com qualquer servidor estático

Variáveis de Ambiente

# .env.local (desenvolvimento — não commitado)
DATABASE_URL=postgresql://localhost:5432/app
NEXTAUTH_SECRET=my-secret
NEXTAUTH_URL=http://localhost:3000

# .env.production (produção)
DATABASE_URL=postgresql://prod:5432/app
NEXTAUTH_SECRET=prod-secret
NEXTAUTH_URL=https://meusite.com
// Acesso a variáveis
// Server-side (qualquer componente/função)
const dbUrl = process.env.DATABASE_URL;

// Client-side (NEXT_PUBLIC_ prefix)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;

// Runtime vs Build-time
// Build-time: process.env.NEXT_PUBLIC_*
// Runtime: process.env.* (apenas server)
// next.config.ts — publicRuntimeConfig (alternative)
const nextConfig = {
  publicRuntimeConfig: {
    apiUrl: process.env.NEXT_PUBLIC_API_URL
  }
};

ISR On Demand

// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidateTag, revalidatePath } from 'next/cache';

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-revalidate-secret');

  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const body = await request.json();

  // Revalidate por tag
  if (body.tag) {
    revalidateTag(body.tag);
  }

  // Revalidate por path
  if (body.path) {
    revalidatePath(body.path);
  }

  return NextResponse.json({ revalidated: true });
}
# Disparar revalidação via webhook
curl -X POST https://meusite.com/api/revalidate \
  -H "Content-Type: application/json" \
  -H "x-revalidate-secret: my-secret" \
  -d '{"tag": "posts"}'

Monitoring

Sentry

npm install @sentry/nextjs
npx @sentry/wizard -i nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.1, // 10% das requisições
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0
});
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.1
});

OpenTelemetry

// instrumentation.ts
import { registerOTel } from '@vercel/otel';

export function register() {
  registerOTel({
    serviceName: 'meu-app-nextjs',
    attributes: {
      environment: process.env.NODE_ENV
    }
  });
}

Logging

// lib/logger.ts
import pino from 'pino';

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV === 'development'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined
});

// Uso
logger.info({ path: req.url }, 'Request received');
logger.error({ err }, 'Error processing request');

CI/CD

# .github/workflows/deploy.yml
name: Deploy Next.js

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run build
      - run: npm test

  deploy-vercel:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - 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'

Lab: Exercício - Deploy Completo

# 1. Build de produção
npm run build

# 2. Testar localmente em produção
npm start

# 3. Docker build
docker build -t my-nextjs-app .
docker run -p 3000:3000 -e DATABASE_URL=$DATABASE_URL my-nextjs-app

# 4. Deploy na Vercel
vercel --prod

# 5. Configurar domínio e variáveis
vercel domains add meusite.com
vercel env add DATABASE_URL

# 6. Verificar health
curl https://meusite.com/api/health
# Logs e monitoramento
vercel logs
docker logs -f my-nextjs-app

Vercel é a plataforma mais integrada para Next.js. Docker com output: 'standalone' é necessário para self-hosted. ISR on demand com webhooks permite atualizar conteúdo sem rebuild. Sentry captura erros em produção com source maps.