🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Módulo 2 - Aula 5

Deploy e Produção

Deploy profissional de aplicações tRPC: Vercel, Docker, CI/CD, monitoramento e otimizações para alta performance em produção.

70 min
Avançado
Deploy

🎯 Por que deploy profissional é crucial para tRPC?

Performance: Configurações de produção otimizam drasticamente a velocidade das APIs tRPC.

Confiabilidade: CI/CD e monitoramento garantem alta disponibilidade e detecção precoce de problemas.

⚙️ Configuração de Produção

next.config.js
// 📁 next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 🚀 Configurações de performance
  experimental: {
    appDir: true,
    serverComponentsExternalPackages: ['@prisma/client'],
  },

  // 📦 Compressão e otimização
  compress: true,
  
  // 🖼️ Otimização de imagens
  images: {
    domains: ['images.unsplash.com', 'avatars.githubusercontent.com'],
    formats: ['image/webp', 'image/avif'],
  },

  // 🔒 Headers de segurança
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'Referrer-Policy',
            value: 'origin-when-cross-origin',
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=()',
          },
        ],
      },
      {
        source: '/api/(.*)',
        headers: [
          {
            key: 'Access-Control-Allow-Origin',
            value: process.env.NODE_ENV === 'production' 
              ? 'https://yourdomain.com' 
              : '*',
          },
          {
            key: 'Access-Control-Allow-Methods',
            value: 'GET, POST, PUT, DELETE, OPTIONS',
          },
          {
            key: 'Access-Control-Allow-Headers',
            value: 'Content-Type, Authorization',
          },
        ],
      },
    ];
  },

  // 🔄 Redirects e rewrites
  async redirects() {
    return [
      {
        source: '/home',
        destination: '/',
        permanent: true,
      },
    ];
  },

  // 📊 Bundle analyzer (desenvolvimento)
  ...(process.env.ANALYZE === 'true' && {
    webpack: (config) => {
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          openAnalyzer: false,
        })
      );
      return config;
    },
  }),
};

module.exports = nextConfig;

🚀 Deploy na Vercel

vercel.json
# 📄 vercel.json
{
  "version": 2,
  "framework": "nextjs",
  "regions": ["iad1"],
  "env": {
    "NEXTAUTH_URL": "@nextauth-url",
    "NEXTAUTH_SECRET": "@nextauth-secret",
    "DATABASE_URL": "@database-url",
    "GOOGLE_CLIENT_ID": "@google-client-id",
    "GOOGLE_CLIENT_SECRET": "@google-client-secret"
  },
  "build": {
    "env": {
      "NEXT_TELEMETRY_DISABLED": "1"
    }
  },
  "functions": {
    "src/app/api/**": {
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, s-maxage=60, stale-while-revalidate=300"
        }
      ]
    }
  ]
}

# 📝 Comandos de Deploy
# 🔧 Instalar Vercel CLI
npm i -g vercel

# 🚀 Deploy inicial
vercel

# 🔄 Deploy de produção
vercel --prod

# 🏷️ Deploy com alias customizado
vercel --prod --alias=myapp.vercel.app

# 📊 Logs em tempo real
vercel logs myapp.vercel.app

# 🔍 Informações do projeto
vercel inspect myapp.vercel.app

🐳 Containerização com Docker

Dockerfile
# 📁 Dockerfile
FROM node:18-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

# 📦 Instalar dependências
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 🏗️ Build da aplicação
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# 🔧 Gerar Prisma client
RUN npx prisma generate

# 🚀 Build Next.js
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build

# 🏃 Runtime
FROM node:18-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

# 👤 Criar usuário não-root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 📂 Copiar arquivos necessários
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

# 📁 Copiar build com permissões corretas
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# 🔐 Mudar para usuário não-root
USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

# 🚀 Comando de início
CMD ["node", "server.js"]

# 📁 .dockerignore
.next
.git
.gitignore
.dockerignore
Dockerfile
node_modules
npm-debug.log
README.md
.env.local
.env.example
coverage
.nyc_output
*.md

🐳 Docker Compose

docker-compose.yml
# 📁 docker-compose.yml
version: '3.8'

services:
  # 🗄️ PostgreSQL Database
  postgres:
    image: postgres:15-alpine
    container_name: trpc-postgres
    environment:
      POSTGRES_DB: trpc_app
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 10s
      retries: 3

  # 🚀 Next.js App
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: trpc-app
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@postgres:5432/trpc_app
      - NEXTAUTH_SECRET=your-secret-here
      - NEXTAUTH_URL=http://localhost:3000
    ports:
      - "3000:3000"
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./prisma:/app/prisma
    command: >
      sh -c "npx prisma migrate deploy &&
             npx prisma db seed &&
             node server.js"

  # 📊 Redis (para cache e rate limiting)
  redis:
    image: redis:7-alpine
    container_name: trpc-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes

volumes:
  postgres_data:
  redis_data:

# 🚀 Comandos úteis
# docker-compose up -d
# docker-compose logs app
# docker-compose exec app sh
# docker-compose down -v

🔄 CI/CD com GitHub Actions

.github/workflows/deploy.yml
# 📁 .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  # 🧪 Testes e Linting
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test_db
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v4

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

      - name: 📦 Install dependencies
        run: npm ci

      - name: 🔍 Type check
        run: npm run type-check

      - name: 🧹 Lint
        run: npm run lint

      - name: 🗄️ Setup database
        run: |
          npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

      - name: 🧪 Run tests
        run: npm run test
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

      - name: 🏗️ Build check
        run: npm run build
        env:
          SKIP_ENV_VALIDATION: true

  # 🚀 Deploy para Vercel
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v4

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

      - name: 📦 Install Vercel CLI
        run: npm install --global vercel@latest

      - name: 🔍 Pull Vercel Environment
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

      - name: 🏗️ Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: 🚀 Deploy to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: 📊 Run health checks
        run: |
          sleep 30
          curl -f ${{ secrets.PRODUCTION_URL }}/api/health || exit 1

  # 🔔 Notificação
  notify:
    needs: [test, deploy]
    runs-on: ubuntu-latest
    if: always()
    
    steps:
      - name: 📱 Notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          channel: '#deployments'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}
        if: always()

📊 Monitoramento e Observabilidade

src/lib/monitoring.ts
// 📁 src/lib/monitoring.ts
import { NextRequest } from 'next/server';

// 📊 Interface para métricas
interface Metric {
  name: string;
  value: number;
  tags?: Record<string, string>;
  timestamp?: Date;
}

// 📈 Classe para coleta de métricas
export class MetricsCollector {
  private metrics: Metric[] = [];

  // ⏱️ Medir duração de operações
  async measureDuration<T>(
    name: string,
    operation: () => Promise<T>,
    tags?: Record<string, string>
  ): Promise<T> {
    const start = Date.now();
    
    try {
      const result = await operation();
      const duration = Date.now() - start;
      
      this.recordMetric({
        name: name + '.duration',
        value: duration,
        tags: { ...tags, status: 'success' },
      });
      
      return result;
    } catch (error) {
      const duration = Date.now() - start;
      
      this.recordMetric({
        name: name + '.duration',
        value: duration,
        tags: { ...tags, status: 'error' },
      });
      
      throw error;
    }
  }

  // 📊 Registrar métrica
  recordMetric(metric: Metric) {
    this.metrics.push({
      ...metric,
      timestamp: new Date(),
    });
    
    // 🔄 Enviar para serviço de monitoramento
    this.sendToMonitoring(metric);
  }

  // 📤 Enviar para serviço externo
  private async sendToMonitoring(metric: Metric) {
    if (process.env.NODE_ENV !== 'production') return;

    try {
      // 📊 Exemplo: DataDog, New Relic, Prometheus
      await fetch(process.env.METRICS_ENDPOINT!, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + process.env.METRICS_API_KEY,
        },
        body: JSON.stringify(metric),
      });
    } catch (error) {
      console.error('Failed to send metric:', error);
    }
  }
}

// 🏥 Health check endpoint
export async function healthCheck() {
  const checks = {
    database: false,
    redis: false,
    external_apis: false,
  };

  try {
    // 🗄️ Verificar conexão com database
    const { prisma } = await import('../server/db/client');
    await prisma.$queryRaw('SELECT 1');
    checks.database = true;
  } catch (error) {
    console.error('Database health check failed:', error);
  }

  try {
    // 📊 Verificar Redis (se usar)
    // await redis.ping();
    checks.redis = true;
  } catch (error) {
    console.error('Redis health check failed:', error);
  }

  try {
    // 🌐 Verificar APIs externas críticas
    const response = await fetch('https://api.external-service.com/health', {
      timeout: 5000,
    });
    checks.external_apis = response.ok;
  } catch (error) {
    console.error('External API health check failed:', error);
  }

  const isHealthy = Object.values(checks).every(Boolean);
  
  return {
    status: isHealthy ? 'healthy' : 'unhealthy',
    checks,
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
  };
}

// 📁 src/app/api/health/route.ts
import { NextResponse } from 'next/server';
import { healthCheck } from '@/lib/monitoring';

export async function GET() {
  try {
    const health = await healthCheck();
    
    return NextResponse.json(health, {
      status: health.status === 'healthy' ? 200 : 503,
    });
  } catch (error) {
    return NextResponse.json(
      {
        status: 'error',
        message: 'Health check failed',
        timestamp: new Date().toISOString(),
      },
      { status: 500 }
    );
  }
}

⚡ Otimizações de Performance

src/server/trpc/trpc-optimized.ts
// 📁 src/server/trpc/trpc.ts (otimizada)
import { TRPCError, initTRPC } from '@trpc/server';
import { type Context } from './context';
import superjson from 'superjson';
import { MetricsCollector } from '@/lib/monitoring';

const t = initTRPC.context<Context>().create({
  transformer: superjson,
  
  // 🚀 Configurações de performance
  isDev: process.env.NODE_ENV === 'development',
  
  // 📊 Formatação de erros otimizada
  errorFormatter({ shape, error }) {
    return {
      ...shape,
      data: {
        ...shape.data,
        // 🔍 Apenas incluir detalhes em desenvolvimento
        stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
        zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
      },
    };
  },
});

// 📊 Middleware de performance
const performanceMiddleware = t.middleware(async ({ path, type, next, ctx }) => {
  const metrics = new MetricsCollector();
  
  return await metrics.measureDuration(
    'trpc.' + type + '.' + path,
    async () => {
      const result = await next();
      
      // 📈 Registrar métricas adicionais
      metrics.recordMetric({
        name: 'trpc.request',
        value: 1,
        tags: {
          path,
          type,
          status: result.ok ? 'success' : 'error',
          userId: ctx.session?.user?.id || 'anonymous',
        },
      });
      
      return result;
    },
    {
      path,
      type,
      userId: ctx.session?.user?.id || 'anonymous',
    }
  );
});

// 💾 Middleware de cache
const cacheMiddleware = t.middleware(async ({ path, type, next, ctx, input }) => {
  // 🔍 Apenas cache para queries
  if (type !== 'query') {
    return next();
  }

  const cacheKey = 'trpc:' + path + ':' + JSON.stringify(input) + ':' + (ctx.session?.user?.id || 'anonymous');
  
  // 📋 Verificar cache (Redis ideal, mas pode usar Map para demo)
  const cached = await getFromCache(cacheKey);
  if (cached) {
    return {
      ok: true,
      data: cached,
    };
  }

  const result = await next();
  
  // 💾 Salvar no cache se sucesso
  if (result.ok) {
    await setCache(cacheKey, result.data, 60); // 60 segundos
  }
  
  return result;
});

// 🔧 Cache helpers (versão simples)
const cache = new Map<string, { data: any; expires: number }>();

async function getFromCache(key: string) {
  const item = cache.get(key);
  if (!item || Date.now() > item.expires) {
    cache.delete(key);
    return null;
  }
  return item.data;
}

async function setCache(key: string, data: any, ttlSeconds: number) {
  cache.set(key, {
    data,
    expires: Date.now() + (ttlSeconds * 1000),
  });
}

// 📦 Procedures otimizados
export const publicProcedure = t.procedure.use(performanceMiddleware);
export const cachedProcedure = t.procedure.use(performanceMiddleware).use(cacheMiddleware);
export const protectedProcedure = publicProcedure.use(enforceUserIsAuthed);

📝 Scripts de Deploy

package.json (scripts)
{
  "scripts": {
    // 🏗️ Build
    "build": "next build",
    "start": "next start",
    "dev": "next dev",
    
    // 🧪 Testes
    "test": "jest",
    "test:watch": "jest --watch",
    "test:e2e": "playwright test",
    
    // 🔍 Linting e formatação
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    
    // 🏷️ Type checking
    "type-check": "tsc --noEmit",
    
    // 🗄️ Database
    "db:migrate": "prisma migrate dev",
    "db:deploy": "prisma migrate deploy",
    "db:seed": "prisma db seed",
    "db:studio": "prisma studio",
    "db:generate": "prisma generate",
    "db:reset": "prisma migrate reset",
    
    // 🚀 Deploy
    "deploy": "vercel --prod",
    "deploy:preview": "vercel",
    
    // 🐳 Docker
    "docker:build": "docker build -t trpc-app .",
    "docker:run": "docker run -p 3000:3000 trpc-app",
    "docker:up": "docker-compose up -d",
    "docker:down": "docker-compose down",
    
    // 📊 Análise
    "analyze": "ANALYZE=true npm run build",
    "lighthouse": "lighthouse http://localhost:3000 --output-path=./lighthouse-report.html",
    
    // 🔧 Utilitários
    "clean": "rm -rf .next out coverage",
    "postinstall": "prisma generate"
  }
}

✅ Checklist de Deploy

Variáveis de Ambiente:Todas as secrets configuradas no provider de deploy.

Database:Migrations aplicadas e conexão configurada.

Monitoramento:Health checks, logs e métricas configurados.

Performance:Cache, compressão e otimizações ativadas.

Segurança:Headers de segurança e rate limiting implementados.

CI/CD:Pipeline de testes e deploy automatizado funcionando.

🎉 Parabéns! Você concluiu o Módulo 2

Você agora domina todo o setup profissional de tRPC e está pronto para construir e fazer deploy de aplicações SaaS escaláveis em produção.

Setup Avançado ✓Prisma + Database ✓Context + Middleware ✓NextAuth ✓Deploy + Produção ✓