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.