Alternar tema

Guia completo de monitoramento em produção para Next.js: Sentry, logs e alertas

Easton editorial illustration: deployment dock

Sexta-feira, 21h17, o celular vibrou.

O grupo no WeChat já estava em pânico: “a página de pagamento não abre”, “meu pedido acabou de falhar”, “ficou tudo em branco”. Rodei localmente. Tudo perfeito. Olhei os logs do servidor e só havia uma linha: “Internal Server Error”. O usuário dizia que, depois de clicar no botão de pagamento, a página travava, mas eu não conseguia reproduzir de jeito nenhum.

Passei aquele fim de semana vasculhando logs de deploy da Vercel, publicando versões de teste de novo e, no fim, descobri que o SDK de pagamento de terceiros às vezes dava timeout em produção. O processo inteiro parecia procurar uma agulha no escuro.

Seu app Next.js pode rodar liso no ambiente de desenvolvimento, mas virar uma caixa de surpresas depois do deploy: SSR que às vezes retorna 500, funções de borda que falham sem explicação, APIs cujo tempo de resposta dispara sem você saber onde está o gargalo.

A causa é simples: falta um sistema completo de monitoramento em produção. Este guia mostra, passo a passo, como montar monitoramento para Next.js: do rastreamento de erros com Sentry aos logs estruturados, do monitoramento de desempenho à configuração de alertas. Não é uma pilha de teoria; são configurações e trechos de código que você consegue copiar e adaptar. Depois dele, no próximo problema em produção, você tende a saber antes do usuário.

Por que o Next.js precisa de uma estratégia própria de monitoramento

A característica de “três ambientes” do Next.js

Uma aplicação frontend tradicional roda só no navegador, e os erros aparecem diretamente no console do DevTools. Com Next.js é diferente: o mesmo app roda ao mesmo tempo em três lugares completamente distintos:

  • Cliente (Browser): componentes React no navegador do usuário
  • Servidor (Node.js): renderização SSR, API Routes e Server Actions
  • Rede de borda (Edge Runtime): middleware e funções de borda

Uma funcionalidade de pagamento pode envolver validação de formulário no cliente -> autenticação no middleware -> chamada via Server Action -> API Route consultando o banco de dados -> resposta renderizada no cliente. Se qualquer etapa falhar, o monitoramento tradicional do navegador não enxerga o quadro completo.

No ano passado encontrei um bug bem estranho: usuários relatavam que “a página carregava muito devagar e depois mostrava 500”. O painel Network do navegador confirmava que a requisição estava lenta, mas não dizia exatamente onde. Só depois de integrar o rastreamento distribuído do Sentry descobrimos que, durante a renderização no servidor, uma API de terceiros tinha saído dos 200ms habituais para 8 segundos. Monitoramento só no cliente nunca encontraria esse tipo de problema.

O “efeito caixa-preta” do SSR

Quando a renderização no servidor falha, o usuário geralmente vê apenas uma página 500 vazia. Sem stack trace, sem contexto, sem nada.

Pior ainda são os erros de Hydration. Talvez você já tenha visto este aviso:

Warning: Expected server HTML to contain a matching <div> in <div>

Esse tipo de erro pode ser discreto no ambiente de desenvolvimento, mas em produção pode quebrar toda a interação da página. Sem um sistema de monitoramento, você descobre passivamente quando alguém diz que “a página não responde ao clique”.

Segundo dados da Vercel, erros relacionados a SSR representam cerca de 35% dos problemas em produção com Next.js. Isso fala só de erros, sem contar desempenho: por exemplo, quando o SSR de um componente começa a demorar mais, o usuário sente que a página “ficou lenta”, mas você não sabe onde está o gargalo.

Os quatro pilares de um monitoramento completo

Uma estratégia confiável de monitoramento para Next.js precisa cobrir estes pontos:

Rastreamento de erros
Não basta capturar exceções. Você precisa saber quem disparou o erro (informações do usuário), em qual ambiente (dispositivo, navegador, rede), quais ações foram feitas (breadcrumbs) e quais parâmetros de requisição estavam relacionados.

Monitoramento de desempenho
O LCP (Largest Contentful Paint) passou de 2,5 segundos? A resposta de uma API ficou mais lenta? Qual consulta ao banco travou a requisição inteira?

Gerenciamento de logs
Logs estruturados que permitem buscar rapidamente por tempo, usuário e ID da requisição. No desenvolvimento, saída bonita para depurar; em produção, envio para uma plataforma de logs para análise centralizada.

Configuração de alertas
Se a taxa de erro passar de um limite, avise o time imediatamente. Se aparecer um novo tipo de erro, envie para o Slack. Se houver regressão de desempenho, dispare um alerta automático.

Com esses quatro pilares, quando algo dá errado em produção você deixa de operar no escuro. Vamos atacar um por um.

Integração prática com Sentry: da instalação à configuração avançada

Integração rápida em 5 minutos

O suporte do Sentry a Next.js já está bem maduro, e o assistente oficial faz a maior parte da configuração. Na prática, leva mesmo uns 5 minutos:

# Instalar o SDK
npm install @sentry/nextjs

# Rodar o assistente de configuração
npx @sentry/wizard@latest -i nextjs

O assistente pergunta algumas coisas, como o DSN do projeto no Sentry e se você quer fazer upload de Source Maps, e depois cria automaticamente três arquivos:

  • sentry.client.config.ts - ambiente do navegador
  • sentry.server.config.ts - servidor Node.js
  • sentry.edge.config.ts - Edge Runtime

Ele também modifica o next.config.js e adiciona o plugin webpack do Sentry. Depois de rodar o assistente, o monitoramento básico já está conectado.

Mas isso é só o começo. Produção precisa de uma configuração mais cuidadosa.

Pontos de captura de erro no App Router

Se você usa App Router, há alguns pontos que merecem atenção especial:

Tratamento global de erros

Crie app/global-error.tsx. Essa é a última linha de defesa do App Router:

'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Envia para o Sentry
    Sentry.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h2>Algo deu errado</h2>
          <p>Já registramos este erro e vamos corrigir o quanto antes</p>
          <button onClick={() => reset()}>Tentar novamente</button>
        </div>
      </body>
    </html>
  );
}

Captura de erros em Server Actions

Server Actions são um dos recursos mais fortes do App Router, mas o tratamento de erros costuma ser esquecido:

'use server';

import * as Sentry from '@sentry/nextjs';

export async function createOrder(formData: FormData) {
  return await Sentry.withServerActionInstrumentation(
    'createOrder', // nome da action, aparece no Sentry
    {
      recordResponse: true, // registra dados da resposta
    },
    async () => {
      // Sua lógica de negócio
      const productId = formData.get('productId');
      const order = await db.order.create({
        data: { productId, userId: getCurrentUserId() },
      });
      return order;
    }
  );
}

Depois desse wrapper, qualquer erro dentro da Server Action é reportado automaticamente, e o tempo de execução também fica rastreável.

Otimização para produção

Depois de integrar o Sentry, a primeira fatura do mês pode assustar, porque a configuração padrão reporta todos os eventos. Ajuste a taxa de amostragem:

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

  // Taxa de amostragem do rastreamento de desempenho
  // Desenvolvimento 100%, produção 10% (ajuste conforme o tráfego)
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

  // Amostragem do Session Replay
  replaysSessionSampleRate: 0.1,      // grava 10% das sessões normais
  replaysOnErrorSampleRate: 1.0,      // grava 100% das sessões com erro

  // Identificação do ambiente
  environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',

  // Ignorar erros específicos
  ignoreErrors: [
    // Erro injetado por extensões do navegador
    'ResizeObserver loop limit exceeded',
    // Erros de scripts de terceiros
    /chrome-extension/,
    /^Non-Error promise rejection/,
  ],
});

Guia para definir tracesSampleRate:

  • UV diário < 10 mil: 0.2 - 0.5
  • UV diário entre 10 mil e 100 mil: 0.1 - 0.2
  • UV diário > 100 mil: 0.05 - 0.1

Em um projeto com cerca de 30 mil UV diários, eu uso 0.15. Isso consome mais ou menos 60% da cota mensal do Sentry: cobertura suficiente sem estourar o orçamento.

Source Maps: depurável sem expor o código

O JavaScript em produção normalmente fica minificado e ofuscado. A stack de erro acaba parecendo isto:

at r.render (app.js:1:23456)

Não dá para entender nada. Source Maps mapeiam o código ofuscado de volta para o código original, mas expor Source Maps publicamente pode vazar o código-fonte.

A abordagem do Sentry é: fazer upload dos Source Maps para os servidores do Sentry. O navegador do usuário não acessa esses arquivos; só o próprio Sentry usa para reconstruir a stack.

Configure isso no CI/CD. Exemplo com GitHub Actions:

# .github/workflows/deploy.yml
- name: Upload Source Maps to Sentry
  env:
    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
    SENTRY_ORG: your-org
    SENTRY_PROJECT: your-project
  run: npm run build

O plugin do Sentry no next.config.js cuida do upload automaticamente. Lembre-se de adicionar SENTRY_AUTH_TOKEN ao GitHub Secrets e nunca commitar esse token no repositório.

Recursos avançados: Session Replay e rastreamento distribuído

Session Replay é um dos recursos de que mais gosto: ele reproduz as ações do usuário, quase como assistir a uma gravação do problema.

Uma vez um usuário disse que “não conseguia clicar no botão de pagamento”. Ao ver o Session Replay, descobri que ele usava um iPad na horizontal e que o botão ficava coberto pelo teclado virtual. Só logs de erro nunca mostrariam isso.

Ativar é simples. Adicione isto à configuração do cliente:

import * as Sentry from '@sentry/nextjs';
import { Replay } from '@sentry/nextjs';

Sentry.init({
  integrations: [
    new Replay({
      maskAllText: false,           // ocultar todos os textos?
      blockAllMedia: true,          // bloquear todas as mídias?
      maskAllInputs: true,          // ocultar inputs de formulário para evitar vazamento de dados sensíveis
    }),
  ],
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
});

Rastreamento distribuído acompanha o ciclo de vida completo de uma requisição. O usuário clica no botão -> o frontend envia a requisição -> a API Route consulta o banco -> o resultado volta para renderização no frontend. Você enxerga o tempo gasto em cada etapa.

A configuração também não é complicada: garanta que frontend e backend usem uma configuração compatível de Sentry.init. O SDK do Sentry passa automaticamente o cabeçalho sentry-trace nas requisições.

Contexto personalizado: deixe o erro mais útil

Por padrão, o Sentry só sabe que “um erro aconteceu”. Com contexto personalizado, o relatório fica muito mais valioso:

import * as Sentry from '@sentry/nextjs';

// Define informações do usuário
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username,
  // Não coloque senha ou outros dados sensíveis aqui!
});

// Adiciona contexto de negócio
Sentry.setContext('purchase', {
  orderId: '12345',
  amount: 99.99,
  paymentMethod: 'credit_card',
});

// Adiciona tags para facilitar filtros
Sentry.setTag('feature', 'checkout');
Sentry.setTag('ab_test', 'variant_b');

Assim, quando o erro acontece, você sabe imediatamente qual usuário foi afetado e em qual cenário de negócio.

Caso real: uma vez percebemos que um erro de pagamento aparecia com frequência acima do esperado. Ao olhar o contexto no Sentry, todos os eventos vinham de usuários com ab_test: variant_b. Localizamos um bug na nova versão do fluxo de pagamento do teste A/B e desativamos a variante na hora, evitando uma perda maior.

Gerenciamento de logs: faça os logs trabalharem por você

console.log deixou de ser suficiente

No começo eu também espalhava console.log por todo lado. Na depuração local parece ótimo, mas em produção isso trava rápido:

  • Não dá para filtrar: encontrar a requisição de um usuário no meio de 100 mil linhas de log? Boa sorte.
  • Não dá para agregar: quer saber quantas consultas ao banco demoraram mais de 1 segundo na última hora? Não há estatística.
  • Não dá para alertar: apareceu “Payment failed” nos logs? Ninguém fica sabendo.

Logs estruturados resolvem esses problemas. Em vez de imprimir uma string solta, você emite objetos JSON. Cada log carrega timestamp, nível, ID da requisição, ID do usuário e outros metadados. Depois, qualquer campo pode ser usado para busca e agregação.

Pino vs Winston: qual escolher

No ecossistema Node.js, as duas grandes bibliotecas de log são Pino e Winston. Cada uma tem seu espaço:

CaracterísticaPinoWinston
DesempenhoMuito rápido; logs assíncronos com custo quase nuloUm pouco mais lento, mas ainda suficiente
Facilidade de usoConfiguração simples, funciona bem logo de caraMais recursos e bom ecossistema de plugins
ExtensibilidadeExpande via TransportInclui vários Transports
ComunidadeRecomendado na documentação do Next.jsBiblioteca tradicional, documentação muito completa

Minha sugestão:

  • Cenários de alta concorrência (QPS > 1000): escolha Pino, a vantagem de desempenho é clara
  • Processamento de logs mais complexo (vários formatos e destinos): escolha Winston
  • Não sabe qual usar: escolha Pino; até a documentação do Next.js costuma usá-lo

Configuração prática com Pino

Instale primeiro:

npm install pino
npm install pino-pretty --save-dev  # saída mais legível no desenvolvimento

Crie um logger global:

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

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',

  // Formata o nível do log
  formatters: {
    level: (label) => ({ level: label.toUpperCase() }),
  },

  // No desenvolvimento, usa pino-pretty para melhorar a leitura
  transport: process.env.NODE_ENV === 'development'
    ? {
        target: 'pino-pretty',
        options: {
          colorize: true,
          translateTime: 'HH:MM:ss',
          ignore: 'pid,hostname',
        },
      }
    : undefined,
});

export { logger };

No desenvolvimento, a saída fica colorida e fácil de ler. Em produção, ela sai como JSON para ser processada por plataformas de log.

Uso em uma API Route

O ponto central é dar a cada requisição um correlationId. Esse ID conecta todos os logs relacionados à mesma requisição:

// app/api/products/route.ts
import { logger } from '@/lib/logger';
import { randomUUID } from 'crypto';

export async function GET(request: Request) {
  // Gera um ID único para a requisição
  const correlationId = request.headers.get('x-correlation-id') || randomUUID();

  // Cria um logger filho que já carrega o correlationId
  const log = logger.child({ correlationId });

  try {
    log.info({ url: request.url }, 'Processando requisição de produtos');

    const products = await db.product.findMany();

    log.info({ count: products.length }, 'Produtos buscados com sucesso');

    return Response.json(products);
  } catch (error) {
    log.error({ error: error.message, stack: error.stack }, 'Falha ao buscar produtos');
    throw error;
  }
}

Assim, todos os logs com o mesmo correlationId podem ser vinculados. Na hora de investigar, basta buscar esse ID e ver a cadeia completa da requisição.

Boas práticas de níveis de log

Log de menos não ajuda; log demais esconde o que importa. Eu costumo separar assim:

ERROR - problema que exige ação imediata

  • Falha de conexão com banco de dados
  • Falha ao chamar gateway de pagamento
  • Exceção em lógica crítica de negócio
log.error({ error, userId, orderId }, 'Falha ao processar pagamento');

WARN - situação anormal, mas recuperável

  • Chamada de API que só deu certo depois de retry
  • Ativação de lógica de fallback
  • Limite de cota próximo
log.warn({ retryCount: 3 }, 'Retry de API externa concluído com sucesso');

INFO - pontos importantes do negócio

  • Login/logout de usuário
  • Criação/conclusão de pedido
  • Alteração importante de configuração
log.info({ userId, ip }, 'Usuário fez login');

DEBUG - informações detalhadas de depuração

  • Parâmetros e retorno de funções
  • Estados intermediários
  • Medições de tempo
log.debug({ params }, 'Chamando API externa');

Em produção, use INFO como padrão. Quando houver problema, suba temporariamente para DEBUG para investigar.

Agregação e análise de logs

No desenvolvimento local, pino-pretty basta. Em produção, você precisa de uma plataforma de logs. Algumas opções comuns:

Vercel Logs
Se o deploy está na Vercel, o sistema de logs vem embutido e sem configuração. O limite é que ele guarda só 7 dias e a busca é simples.

Datadog
Solução corporativa com APM, logs e monitoramento no mesmo pacote. Configuração:

import { datadogLogs } from '@datadog/browser-logs';

datadogLogs.init({
  clientToken: process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN,
  site: 'datadoghq.com',
  forwardErrorsToLogs: true,
  sampleRate: 100,
});

Logtail/BetterStack
Boa relação custo-benefício, com foco em análise de logs. Suporta busca em tempo real, regras de alerta e painéis personalizados.

Em projetos pessoais eu uso Logtail: é simples de configurar e oferece 1 GB de logs gratuitos por mês. Em projetos de time, uso Datadog. É caro, mas muito completo.

Campos importantes em um log

Um bom log deve incluir estes campos:

{
  "timestamp": "2025-12-20T15:00:06.123Z",  // timestamp
  "level": "INFO",                          // nível do log
  "correlationId": "abc-123-def",           // ID de correlação da requisição
  "userId": "user_456",                     // ID do usuário
  "action": "create_order",                 // ação de negócio
  "duration": 234,                          // duração (ms)
  "status": "success",                      // status
  "metadata": {                             // metadados adicionais
    "orderId": "order_789",
    "amount": 99.99
  }
}

Esse tipo de log responde à pergunta: quem fez o quê, quando, e com qual resultado.

Monitoramento de desempenho: otimização guiada por dados

Core Web Vitals: as métricas que o Google leva em conta

O Google usa Core Web Vitals como fator de ranqueamento, então você também deveria acompanhar:

  • LCP (Largest Contentful Paint): tempo do maior conteúdo visível; ideal < 2,5s
  • FID (First Input Delay) / INP (Interaction to Next Paint): tempo de resposta da interação; < 100ms / < 200ms
  • CLS (Cumulative Layout Shift): deslocamento cumulativo de layout; < 0,1

O Next.js já inclui envio de Web Vitals. Basta adicionar algumas linhas em app/layout.tsx:

'use client';

import { useReportWebVitals } from 'next/web-vitals';

export function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    // Envia para o Sentry
    if (window.Sentry) {
      window.Sentry.captureMessage(`Web Vital: ${metric.name}`, {
        level: 'info',
        tags: {
          web_vital: metric.name,
        },
        contexts: {
          web_vitals: {
            value: metric.value,
            rating: metric.rating,
          },
        },
      });
    }

    // Ou envia para sua plataforma de analytics
    fetch('/api/analytics/web-vitals', {
      method: 'POST',
      body: JSON.stringify(metric),
    });
  });

  return null;
}

Depois, importe no layout raiz:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <WebVitalsReporter />
        {children}
      </body>
    </html>
  );
}

Rastreamento de desempenho em APIs

Desempenho do frontend é só metade da história. Se a API no backend estiver lenta, o usuário também espera. O Performance Monitoring do Sentry rastreia cada requisição de API:

// app/api/products/[id]/route.ts
import * as Sentry from '@sentry/nextjs';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  // Cria uma transaction
  return await Sentry.startSpan(
    {
      op: 'api.request',
      name: 'GET /api/products/[id]',
    },
    async () => {
      // Rastreia a consulta ao banco
      const product = await Sentry.startSpan(
        {
          op: 'db.query',
          name: 'Fetch product from database',
        },
        async () => {
          return await db.product.findUnique({
            where: { id: params.id },
            include: { reviews: true },
          });
        }
      );

      if (!product) {
        return Response.json({ error: 'Not found' }, { status: 404 });
      }

      // Rastreia a chamada a uma API externa
      const pricing = await Sentry.startSpan(
        {
          op: 'http.client',
          name: 'Fetch pricing from external API',
        },
        async () => {
          const res = await fetch(`https://pricing-api.com/product/${params.id}`);
          return res.json();
        }
      );

      return Response.json({ ...product, pricing });
    }
  );
}

No painel do Sentry, você vê algo assim:

  • Requisição completa: 450ms
    • Consulta ao banco: 120ms
    • Chamada à API externa: 300ms
    • Outras lógicas: 30ms

Fica visível de cara: o gargalo está na API externa.

Alerta de consultas lentas

Banco de dados costuma ser gargalo de desempenho. Você pode adicionar monitoramento no middleware do Prisma:

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
import { logger } from './logger';

const prisma = new PrismaClient();

// Monitora consultas lentas
prisma.$use(async (params, next) => {
  const before = Date.now();
  const result = await next(params);
  const after = Date.now();
  const duration = after - before;

  // Consultas acima de 1 segundo são registradas como WARN
  if (duration > 1000) {
    logger.warn({
      model: params.model,
      action: params.action,
      duration,
      args: params.args,
    }, 'Consulta lenta ao banco detectada');

    // Também envia para o Sentry
    Sentry.captureMessage('Consulta lenta ao banco', {
      level: 'warning',
      tags: { model: params.model, action: params.action },
      extra: { duration, args: params.args },
    });
  }

  return result;
});

export { prisma };

Com isso, nenhuma consulta lenta passa despercebida.

Monitoramento real de usuários vs monitoramento sintético

Monitoramento real de usuários (RUM)
Coleta dados de desempenho de usuários reais com Sentry, Datadog e ferramentas parecidas. A vantagem é refletir cenários reais, com redes e dispositivos diferentes. A desvantagem é ser passivo: você só descobre depois que o problema aconteceu.

Monitoramento sintético (Synthetic Monitoring)
Usa Checkly, Pingdom e similares para simular acessos periódicos ao seu site a partir de diferentes regiões. A vantagem é encontrar problemas de forma ativa. A desvantagem é não cobrir todos os cenários de usuário.

O melhor é combinar os dois:

  • RUM acompanha métricas de experiência do usuário
  • Monitoramento sintético cuida de disponibilidade e fluxos críticos, como login e pagamento

Eu uso Checkly para acessar a página inicial e a API de login a cada 5 minutos a partir de 5 regiões. Se qualquer uma delas falha ou passa do timeout, o alerta chega na hora.

Configuração de alertas: descubra o problema no primeiro minuto

Integração com Slack: avise o time rapidamente

A integração do Sentry com Slack é simples: Settings -> Integrations -> Slack. Depois da autorização, escolha o canal que receberá as notificações.

Mas a configuração padrão pode enviar todos os erros e virar ruído rapidamente. Você precisa configurar regras de alerta:

Nas configurações do projeto no Sentry:

  1. Alerts -> Create Alert Rule
  2. Escolha a condição de disparo:
    • Alerta por taxa de erro: “mais de 50 erros em 10 minutos”
    • Alerta de erro novo: “primeira ocorrência do erro envia notificação imediata”
    • Regressão de desempenho: “P95 de resposta da API > 1s”
  3. Escolha a ação: Send a notification via Slack

Formato da mensagem no Slack:

🚨 Production Error Spike

Project: my-nextjs-app
Environment: production
Error: TypeError: Cannot read property 'id' of undefined
Events: 127 events in 10 minutes

View in Sentry: https://sentry.io/...

O link leva direto ao Sentry, com detalhes e stack trace.

Classificação de alertas: evite fadiga

Nem todo problema tem a mesma urgência. Minha estratégia de classificação:

P0 - Crítico (ação imediata)

  • Serviço totalmente indisponível
  • Falha no pagamento
  • Conexão com banco de dados perdida

Disparo: telefone (PagerDuty) + Slack @channel

P1 - Importante (resposta em até 1 hora)

  • Função central com erro
  • Pico na taxa de erros (mais de 100 em 10 minutos)
  • P95 de resposta da API > 3s

Disparo: mensagem no canal de desenvolvimento no Slack

P2 - Normal (tratar em horário útil)

  • Erro de pequeno alcance (< 10 vezes/hora)
  • Função não crítica com problema
  • Erro em script de terceiros

Disparo: resumo diário por e-mail

Exemplo de configuração (Sentry Alert Rule):

// Alerta P0: falha de pagamento
{
  conditions: [
    { type: 'event.tag', key: 'feature', value: 'payment' },
    { type: 'event.level', value: 'error' }
  ],
  frequency: 'every event',  // notifica em todos os eventos
  actions: [
    { type: 'slack', channel: '#critical-alerts', mention: '@channel' },
    { type: 'pagerduty', service: 'payments' }
  ]
}

// Alerta P1: pico na taxa de erros
{
  conditions: [
    { type: 'event.count', value: 100, interval: '10m' }
  ],
  frequency: 'once per issue',  // notifica uma vez por problema
  actions: [
    { type: 'slack', channel: '#alerts-dev' }
  ]
}

Técnicas para reduzir ruído de alerta

Logo depois de integrar monitoramento, você pode ser inundado por alertas. Algumas técnicas ajudam:

1. Ignore problemas conhecidos

Erros de hot reload no desenvolvimento e exceções de scripts de terceiros podem ser filtrados:

// sentry.client.config.ts
Sentry.init({
  ignoreErrors: [
    // Erros de extensões do navegador
    /chrome-extension/,
    /moz-extension/,
    // Scripts de terceiros
    /google-analytics/,
    // Hot reload no desenvolvimento
    /HMR/,
  ],
  denyUrls: [
    // Ignora erros de scripts de domínios específicos
    /extensions\//i,
    /^chrome:\/\//i,
  ],
});

2. Agrupe alertas repetidos

Notifique o mesmo erro só uma vez dentro de 10 minutos, em vez de inundar o canal. O “Issue Grouping” do Sentry já agrupa automaticamente erros parecidos.

3. Defina períodos de silêncio

Durante um deploy, pode haver um pico breve de erros. Você pode usar “Mute for 10 minutes”.

4. Use fingerprint

Personalize regras de agrupamento para juntar erros com a mesma causa raiz:

Sentry.captureException(error, {
  fingerprint: ['database-connection-error', databaseName],
});

Assim, erros de conexão em bancos diferentes ficam separados, o que facilita a localização do problema.

Caso prático: implantando uma estratégia completa de monitoramento

Arquitetura de monitoramento para e-commerce

No ano passado, ajudei um e-commerce a reformular o monitoramento. Esta foi a solução completa:

Contexto:

  • UV diário médio de 80 mil
  • QPS acima de 3000 nos picos
  • Principais problemas: falhas ocasionais no pagamento e página inicial lenta

Arquitetura de monitoramento:

┌─────────────┐
│   Next.js   │
│   frontend/SSR  │
└──────┬──────┘
       │
       ├─ Sentry (erros + desempenho)
       ├─ Pino (logs estruturados) → Datadog
       ├─ Web Vitals → Sentry
       └─ Checkly (monitoramento sintético)

Configurações principais:

  1. Rastreamento de comportamento do usuário
// lib/tracking.ts
import * as Sentry from '@sentry/nextjs';

export function trackCheckoutStep(step: string, data: any) {
  Sentry.addBreadcrumb({
    category: 'checkout',
    message: `Etapa do checkout: ${step}`,
    data,
    level: 'info',
  });
}

// Chame durante o fluxo de compra
trackCheckoutStep('add_to_cart', { productId, price });
trackCheckoutStep('proceed_to_payment', { cartTotal });
trackCheckoutStep('payment_submitted', { method: 'credit_card' });

Quando o pagamento falha, você consegue ver o caminho completo do usuário até ali.

  1. Monitoramento de pagamento
// app/api/payment/route.ts
export async function POST(request: Request) {
  const log = logger.child({ action: 'payment' });

  try {
    const result = await processPayment(data);

    log.info({ orderId, amount, method }, 'Pagamento concluído com sucesso');

    return Response.json({ success: true, orderId });
  } catch (error) {
    log.error({ error, orderId, userId }, 'Falha no pagamento');

    // Alerta P0
    Sentry.captureException(error, {
      tags: { feature: 'payment', severity: 'critical' },
      level: 'fatal',
    });

    return Response.json({ error: 'Payment failed' }, { status: 500 });
  }
}

Qualquer falha de pagamento avisa o time imediatamente.

  1. Definição de linha de base de desempenho

Use o Sentry Performance Monitoring para criar a linha de base:

  • LCP da página inicial < 2s
  • LCP da página de produto < 2,5s
  • P95 da API /api/products < 500ms

Quando passar da linha de base, dispare alerta automático.

Resultado:

  • Tempo médio de detecção de incidentes caiu de 40 minutos para 3 minutos
  • Taxa de falha em pagamentos caiu de 0,8% para 0,2%
  • LCP da página inicial, depois de otimizações, caiu de 3,2s para 1,8s

Checklist de monitoramento

Para fechar, aqui vai uma lista para revisar seu projeto:

**Monitoramento de erros**
- [ ] Sentry configurado e testado
- [ ] Source Maps enviados com sucesso
- [ ] global-error.tsx criado (App Router)
- [ ] Server Actions com tratamento de erro
- [ ] Regras de ignore configuradas para filtrar ruído

**Gerenciamento de logs**
- [ ] Biblioteca de logs integrada (Pino/Winston)
- [ ] Produção emitindo logs em JSON
- [ ] Logs incluem correlationId
- [ ] Nível de log definido corretamente (INFO em produção)
- [ ] Logs conectados a uma plataforma de agregação

**Monitoramento de desempenho**
- [ ] Envio de Web Vitals ativado
- [ ] Core Web Vitals dentro da meta (LCP<2,5s, INP<200ms, CLS<0,1)
- [ ] APIs críticas com rastreamento de desempenho
- [ ] Monitoramento de consultas lentas configurado
- [ ] Monitoramento sintético configurado (opcional)

**Configuração de alertas**
- [ ] Alertas por Slack/e-mail testados
- [ ] Regras de alerta classificadas por prioridade
- [ ] Regras para redução de ruído configuradas
- [ ] Todo o time conhece o fluxo de alerta
- [ ] Incidentes P0 têm responsáveis claros

**Melhoria contínua**
- [ ] Revisão semanal dos dados de monitoramento
- [ ] Análise de tendência de erros (quais erros estão aumentando)
- [ ] Detecção de regressão de desempenho (quais páginas ficaram lentas)
- [ ] Otimização periódica das regras de alerta

Conclusão

Monitoramento tira você do modo apagar incêndio e coloca a produção sob controle.

Recapitulando o sistema que montamos:

  • Sentry cuida de rastreamento de erros e monitoramento de desempenho, além de poder reproduzir sessões de usuário
  • Pino oferece logs estruturados e conecta toda a cadeia da requisição por correlationId
  • Web Vitals acompanha métricas de experiência do usuário que impactam diretamente o SEO
  • Alertas no Slack fazem o time saber do problema rapidamente, com classificação para evitar fadiga

Mais importante que a ferramenta é a mudança de mentalidade: monitoramento não é “bom ter”; é o airbag da produção. Você não espera o acidente para instalar o airbag. Do mesmo modo, não deveria esperar a produção quebrar para lembrar de monitoramento.

Comece hoje. Se seu projeto ainda não tem monitoramento:

  1. Neste fim de semana, reserve 2 horas para integrar o Sentry e configurar o rastreamento básico de erros
  2. Na semana seguinte, adicione logs estruturados e correlationId
  3. Na outra semana, configure alertas no Slack e linhas de base de desempenho

Não tente acertar tudo de uma vez. Coloque primeiro o monitoramento básico de erros para rodar e depois evolua aos poucos. Depois de cada incidente em produção, pergunte: “o monitoramento poderia ter detectado esse problema antes?” Melhorando continuamente, o monitoramento vira um dos apoios mais confiáveis do time.

Por fim, se este guia ajudou você, compartilhe com o time. Monitoramento é responsabilidade coletiva, não uma batalha individual.

Que seu app Next.js rode com estabilidade e sem quedas. Mas, como a realidade costuma discordar, monitoramento importa de verdade.

Fluxo completo para configurar monitoramento em produção no Next.js

Passo a passo completo da integração com Sentry até logs estruturados, monitoramento de desempenho e alertas

⏱️ Estimated time: 3 hr

  1. 1

    Step 1: Integrar o rastreamento de erros com Sentry

    Instalação:
    ```bash
    npm install @sentry/nextjs
    ```

    Inicialização:
    ```bash
    npx @sentry/wizard@latest -i nextjs
    ```

    Configuração do cliente:
    ```ts
    // sentry.client.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    Configuração do servidor:
    ```ts
    // sentry.server.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    Pontos-chave:
    • Configure cliente e servidor separadamente
    • Use tracesSampleRate para controlar a taxa de amostragem
    • Configure as variáveis de ambiente
  2. 2

    Step 2: Configurar logs estruturados

    Use correlationId para vincular uma requisição:
    ```ts
    // middleware.ts
    import { v4 as uuidv4 } from 'uuid'

    export function middleware(request: NextRequest) {
    const correlationId = request.headers.get('x-correlation-id') || uuidv4()

    const response = NextResponse.next()
    response.headers.set('x-correlation-id', correlationId)

    return response
    }
    ```

    Uso nos logs:
    ```ts
    import { headers } from 'next/headers'

    export async function handler() {
    const headersList = headers()
    const correlationId = headersList.get('x-correlation-id')

    console.log({
    correlationId,
    message: 'Ação do usuário',
    timestamp: new Date().toISOString(),
    })
    }
    ```

    Pontos-chave:
    • Gere um ID único para cada requisição
    • Inclua correlationId em todos os logs
    • Facilite o rastreamento de toda a cadeia da requisição
  3. 3

    Step 3: Configurar monitoramento de desempenho

    Sentry APM:
    ```ts
    Sentry.init({
    tracesSampleRate: 1.0, // 100% de amostragem
    integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    ],
    })
    ```

    Monitoramento de desempenho personalizado:
    ```ts
    const transaction = Sentry.startTransaction({
    op: 'http.server',
    name: 'API Route',
    })

    try {
    // Lógica de negócio
    await processRequest()
    } finally {
    transaction.finish()
    }
    ```

    Pontos-chave:
    • Use tracesSampleRate para controlar a taxa de amostragem
    • Monitore o tempo de resposta das APIs
    • Identifique gargalos de desempenho
  4. 4

    Step 4: Configurar alertas

    Alertas no Sentry:
    • Configure regras de alerta no Sentry Dashboard
    • Defina limites de erro
    • Configure canais de notificação (Slack, e-mail etc.)

    Integração com Slack:
    ```ts
    // Configuração no Sentry Dashboard
    // Webhook URL: https://hooks.slack.com/services/...
    ```

    Alertas por e-mail:
    • Configure no Sentry Dashboard
    • Defina destinatários
    • Configure condições de alerta

    Pontos-chave:
    • Defina limites de alerta razoáveis
    • Evite fadiga de alertas
    • Responda aos alertas rapidamente

FAQ

Por que o Next.js precisa de uma estratégia própria de monitoramento?
O motivo é a característica de "três ambientes" do Next.js.

O mesmo app roda em três lugares ao mesmo tempo:
• Cliente (Browser): componentes React no navegador do usuário
• Servidor (Node.js): renderização SSR, API Routes e Server Actions
• Rede de borda (Edge Runtime): middleware e funções de borda

O monitoramento frontend tradicional enxerga apenas erros do cliente. Ele não vê erros do servidor nem da borda.

Efeito de caixa-preta do SSR:
• Quando a renderização no servidor falha, o usuário só vê uma página 500
• Não há stack trace nem contexto
• É preciso usar rastreamento distribuído com Sentry para encontrar o problema

Caso real:
• O usuário relata: "a página carrega muito devagar e depois mostra 500"
• O painel Network do navegador mostra que a requisição está lenta, mas não mostra onde está a lentidão
• Depois de integrar o Sentry, ficou claro que uma chamada a uma API de terceiros no servidor tinha saído de 200ms para 8 segundos

Solução: você precisa de um sistema completo de monitoramento que cubra cliente, servidor e rede de borda.
Como integrar o Sentry?
Instalação:
```bash
npm install @sentry/nextjs
```

Inicialização:
```bash
npx @sentry/wizard@latest -i nextjs
```

Configuração do cliente:
```ts
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

Configuração do servidor:
```ts
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

Pontos-chave:
• Configure cliente e servidor separadamente
• Use tracesSampleRate para controlar a taxa de amostragem
• Configure as variáveis de ambiente (NEXT_PUBLIC_SENTRY_DSN, SENTRY_DSN)
Como configurar logs estruturados?
Use correlationId para vincular uma requisição:

Gere no middleware:
```ts
import { v4 as uuidv4 } from 'uuid'

export function middleware(request: NextRequest) {
const correlationId = request.headers.get('x-correlation-id') || uuidv4()

const response = NextResponse.next()
response.headers.set('x-correlation-id', correlationId)

return response
}
```

Use nos logs:
```ts
import { headers } from 'next/headers'

export async function handler() {
const headersList = headers()
const correlationId = headersList.get('x-correlation-id')

console.log({
correlationId,
message: 'Ação do usuário',
timestamp: new Date().toISOString(),
})
}
```

Vantagens:
• Cada requisição recebe um ID único
• Todos os logs carregam o correlationId
• Fica mais fácil rastrear toda a cadeia da requisição
• A localização do problema é mais rápida

Ponto-chave: gere no middleware e use nos logs para facilitar a correlação.
Como configurar monitoramento de desempenho?
Sentry APM:
```ts
Sentry.init({
tracesSampleRate: 1.0, // 100% de amostragem (em produção, recomenda-se 0.1)
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
],
})
```

Monitoramento de desempenho personalizado:
```ts
const transaction = Sentry.startTransaction({
op: 'http.server',
name: 'API Route',
})

try {
// Lógica de negócio
await processRequest()
} finally {
transaction.finish()
}
```

Métricas monitoradas:
• Tempo de resposta das APIs
• Tempo de consultas ao banco de dados
• Tempo de chamadas a APIs de terceiros
• Tempo de carregamento da página

Pontos-chave:
• Use tracesSampleRate para controlar a taxa de amostragem (em produção, recomenda-se 0.1)
• Monitore caminhos críticos
• Identifique gargalos de desempenho
Como configurar alertas?
Alertas no Sentry:
• Configure regras de alerta no Sentry Dashboard
• Defina limites de erro (por exemplo: mais de 10 erros em 5 minutos)
• Configure canais de notificação (Slack, e-mail etc.)

Integração com Slack:
• Configure a Webhook URL no Sentry Dashboard
• Defina condições de alerta
• Teste o alerta

Alertas por e-mail:
• Configure no Sentry Dashboard
• Defina destinatários
• Configure condições de alerta

Sugestões de regras de alerta:
• Taxa de erros acima do limite
• Tempo de resposta acima do limite
• Tipos específicos de erro
• Surgimento de um erro novo

Pontos-chave:
• Defina limites de alerta razoáveis
• Evite fadiga de alertas
• Responda aos alertas rapidamente

Sugestão: coloque primeiro o monitoramento básico de erros para rodar e depois evolua aos poucos.
Quais são as melhores práticas de monitoramento?
Implementação gradual:
1. Neste fim de semana, reserve 2 horas para integrar o Sentry e configurar o rastreamento básico de erros
2. Na semana seguinte, adicione logs estruturados e correlationId
3. Na outra semana, configure alertas no Slack e uma linha de base de desempenho

Não tente resolver tudo de uma vez. Coloque primeiro o monitoramento básico de erros para rodar e depois evolua aos poucos.

Melhoria contínua:
• Depois de cada incidente em produção, pergunte: "o monitoramento poderia ter detectado esse problema antes?"
• Ajuste os limites de alerta conforme a situação real
• Revise a configuração de monitoramento periodicamente

Métricas-chave:
• Taxa de erros
• Tempo de resposta
• Escopo de impacto sobre usuários
• Tempo de recuperação

Recomendações:
• Monitoramento é responsabilidade do time, não de uma pessoa só
• Compartilhe dados de monitoramento regularmente
• Melhore o sistema de monitoramento de forma contínua

Lembre-se: monitoramento não é uma tarefa única, mas um processo contínuo.

22 min de leitura · Publicado em: 20 dez 2025 · Atualizado em: 14 jul 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog