Deploy profissional de aplicações tRPC: Vercel, Docker, CI/CD, monitoramento e otimizações para alta performance em produção.
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.
// 📁 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;
# 📄 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
# 📁 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.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
# 📁 .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()
// 📁 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 }
);
}
}
// 📁 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": {
// 🏗️ 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"
}
}
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.
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.