Destino Dev

Blog

Caching avançado no App Router: Cache-Control, tags e Edge para rotas dinâmicas

Por John Pires · 08 de março de 2026

Caching avançado no App Router: Cache-Control, tags e Edge para rotas dinâmicas

Motivação e trade-offs

Para aplicações Next.js com App Router, cache não é só performance: é arquitetura. Decidir entre SSR, ISR, Edge e cache-control define latência, custo e complexidade operacional. Sem uma estratégia clara você vai ter inconsistência de dados, cache stampede, ou custos desnecessários no provedor de CDN.

Este artigo foca em rotas dinâmicas (produtos, feeds, perfis), onde os dados mudam com frequência e onde você precisa de respostas rápidas. Vou te mostrar como combinar headers Cache-Control, as APIs de tags do Next.js (next/cache), e funções em runtime edge para ter controle fino sobre invalidação e tempo de vida do cache.

Trade-offs principais:

  • SSR: sempre fresco, maior latência e custo. Use onde dados são críticos a cada request.

  • ISR: bom balanço entre frescor e custo. Exponha revalidade e invalidação programática quando possível.

  • Edge: latência baixa globalmente, mas ambientes limitados (Node APIs não disponíveis).

  • Cache-Control na CDN: poderoso mas exige alinhamento entre CDN e cache da aplicação.

Escolha baseada em perfil do dado: perfis de usuário sensíveis => SSR ou per-request cache bust; listagens públicas => ISR com stale-while-revalidate; preços => ISR com tags e invalidação imediata.

O objetivo prático

Ao final você terá patterns reutilizáveis: configuração de revalidate no App Router, uso de Cache-Control em API routes para comportamentos CDN-friendly, e uso das APIs createTag / revalidateTag para invalidação seletiva.

Quando evitar cache agressivo

Não use stale-while-revalidate em endpoints que modificam dados críticos (ex: checkout) sem proteção de idempotência e validação no backend — você pode servir HTML desatualizado que leva a erros de cobrança.

Fundamentos técnicos

Os componentes relevantes no App Router são: Server Components com export revalidate, Route Handlers (app/api/...), as utilitárias em next/cache e a possibilidade de executar rotas no runtime edge. Entender como cada camada aplica cache é essencial.

Conceitos-chave:

  1. revalidate: export constante em um segment/component que instrui ISR com TTL (segundos).

  2. Cache-Control: header HTTP que controla comportamento do CDN e do browser. Valores comuns: s-maxage, stale-while-revalidate, max-age, no-store.

  3. Tags: APIs de next/cache para marcar renderizações e permitir invalidação seletiva via revalidateTag.

  4. Edge runtime: export const runtime = 'edge' em route handlers ou server actions para execução em V8/Edge com latência baixa.

Como revalidate funciona

Exportar export const revalidate = 60; em um Server Component sinaliza ao Next que a página pode ser revalidada a cada 60s. O Next entrega a versão cacheada e programa revalidação em segundo plano (stale-while-revalidate behavior). Isso é ótimo para dados que podem ser eventualmente consistentes.

Tags: marcar e invalidar

O fluxo das tags é: criar uma tag com createTag e chamá-la quando renderizar um componente (pratica de associação). Quando precisar forçar atualização, chame revalidateTag('minha-tag') em um API route ou server action. Isso afeta apenas entradas marcadas, evitando revalidar toda a rota.

Exemplo 1: caso simples

Contexto: página de produto simples em app/product/[id]/page.tsx que precisa de cache de 60s e pode ser revalidada por uma API administrativa.

O código abaixo demonstra como configurar revalidate e criar um endpoint de invalidação que chama revalidateTag. Primeiro, o componente de produto.

/* app/product/[id]/page.tsx
- O componente busca dados do produto, marca a renderização com uma tag e exporta revalidate.
- Resultado: HTML é cacheado por 60s; quando revalidateTag('product-') for chamado, essa página é revalida. */

import React from 'react'
import { createTag } from 'next/cache'

export const revalidate = 60 // ISR com TTL de 60s

export default async function ProductPage({ params }: { params: { id: string } }) {
  const { id } = params
  const productTag = createTag(`product-${id}`)

  // marca a renderização atual com a tag
  productTag()

  const res = await fetch(`https://api.example.com/products/${id}`, { next: { revalidate: 60 } })
  const product = await res.json()

  return (
    {product.name}{product.description}
  )
}


O que acontece: a página usa revalidate de 60s e chama a tag — o Next associa o HTML renderizado a product-. O CDN e o Next podem servir a versão cacheada até a revalidação.

Agora o endpoint administrativo que invalida a tag após uma atualização do produto.

/* app/api/revalidate/product/[id]/route.ts
- Endpoint para invalidação manual. Em produção, proteja com autenticação e validação.
- Chamar este endpoint após uma atualização força revalidate da página marcada. */

import { NextResponse } from 'next/server'
import { revalidateTag } from 'next/cache'

export async function POST(req: Request, { params }: { params: { id: string } }) {
  const { id } = params
  // validação mínima (exemplo)
  const body = await req.json()
  if (!body || !body.secret || body.secret !== process.env.REVALIDATE_SECRET) {
    return new NextResponse('unauthorized', { status: 401 })
  }

  // invalida as renderizações marcadas com product-
  revalidateTag(`product-${id}`)

  return NextResponse.json({ revalidated: true })
}

Depois de chamada com sucesso, a próxima request para /product/[id] vai gerar uma nova renderização. Esse padrão é simples e seguro quando você tem controle sobre quem pode chamar a invalidação.

Observações práticas

Use revalidateTag quando atingir partes específicas do site. Para mudanças globais (ex: alterar o tema), prefira revalidatePath ou invalidar múltiplas tags.

Proteja endpoints com autenticação e não exponha secrets em cliente.

Exemplo 2: caso avançado do mundo real

Cenário: e-commerce com catálogo público, preços atualizados frequentemente por sistemas externos e inventário crítico. Objetivos:

  • Resposta global rápida (Edge)

  • Minimizar cache stampede

  • Invalidação seletiva ao atualizar preço/inventário

Solução combinada:

  1. Server Components com tags para cada produto e listagem.

  2. Route handlers no edge para endpoints de publicação que executam revalidateTag e retornam respostas com Cache-Control otimizadas.

  3. Uso de s-maxage + stale-while-revalidate em APIs públicas para CDN-friendly behavior.

Exemplo de route handler edge que publica atualização e ajusta cabeçalhos Cache-Control.

/* app/api/publish/product/route.ts
- Executa em runtime edge para baixa latência global.
- Atualiza o sistema backend, chama revalidateTag e devolve header Cache-Control apropriado.
- Use secrets e validação rigorosa (ex.: JWT, API key). */

export const runtime = 'edge'

import { NextResponse } from 'next/server'
import { revalidateTag } from 'next/cache'

export async function POST(req: Request) {
  const body = await req.json()
  const { id, price } = body

  // valida token/external auth (pseudocódigo)
  const auth = req.headers.get('authorization')
  if (!auth || auth !== `Bearer ${process.env.PUBLISH_KEY}`) {
    return new NextResponse('unauthorized', { status: 401 })
  }

  // atualiza serviço de preços (exemplo) - pode ser outro microserviço
  await fetch(`https://internal.api/prices/${id}`, { method: 'POST', body: JSON.stringify({ price }) })

  // invalida apenas a tag do produto e lista que inclui o produto
  revalidateTag(`product-${id}`)
  revalidateTag('product-list')

  // responder com instruções para CDN: cache por 30s e usar stale-while-revalidate
  return new NextResponse(JSON.stringify({ ok: true }), {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 's-maxage=30, stale-while-revalidate=60'
    }
  })
}

O que acontece: a publicação é rápida porque roda no edge; as tags são revalidadas seletivamente; o header Cache-Control ajuda CDNs a servir a resposta de forma eficiente enquanto propagam a nova versão.

Complemento no client/server: a listagem de produtos deve usar fetch com next: { tags: ['product-list'] } se você quiser que a listagem seja associada a essa tag. Para Server Components, chamar a tag via createTag ao renderizar faz a associação.

/* app/product/list/page.tsx
- Exemplo de listagem que marca a renderização com a tag 'product-list'. */

import { createTag } from 'next/cache'

export const revalidate = 30

export default async function ProductList() {
  const listTag = createTag('product-list')
  listTag()

  const res = await fetch('https://api.example.com/products', { next: { revalidate: 30 } })
  const products = await res.json()

  return (
    
      {products.map((p: any) => (
        {p.name} — {p.price}
      ))}
  )
}


Esse padrão reduz o alcance da invalidação: atualizar preço só revalida a página do produto e a listagem, não todo o site.

Mitigando cache stampede

Stampede acontece quando múltiplas requisições disparam renderizações simultâneas. Boas práticas:

  • Usar revalidate > 0 para permitir que o Next sirva versões stale enquanto revalida.

  • Aplicar jitter na revalidação programada (se você controla cron jobs).

  • Implementar locks em sistemas de build/worker para atualizar dados pesados sob demanda.

Balanceando freshness vs custo

Se atualização imediata é requerida apenas em 1% das mudanças, prefira ISR com tags esperando uma invalidação manual nos casos críticos, e deixe a maioria ficar com TTLs maiores.

Debugging e troubleshooting

Cache é um dos domínios onde erros aparecem fora do ambiente de desenvolvimento. Aqui estão problemas comuns, sinais e como investigá-los.

Sintomas e causas comuns:

  1. Conteúdo desatualizado por mais tempo que o esperado — verifique headers Cache-Control e revalidate settings.

  2. Invalidação não tem efeito — confirme se a tag correta foi criada e usada; revalidateTag precisa do mesmo nome da tag criada.

  3. Routes não executam no Edge — confira export const runtime = 'edge' no arquivo e se o provedor dá suporte a edge functions.

Tip: use ferramentas de rede do browser e curl -I para inspecionar headers Cache-Control e verificar se a CDN está aplicando suas diretivas.

Checklist de investigação:

  • Logs de request -> verifique se o server handler foi acionado após a invalidação.

  • Confirme nomes de tags: espaços e capitalização fazem diferença.

  • Verifique se a revalidação ocorre em background: algumas plataformas mostram jobs pendentes.

  • Em ambientes com múltiplas camadas (Edge CDN + App CDN), alinhe cabeçalhos entre elas.

Erros comuns

Erros frequentes que vejo em times intermediários:

  • Chamar createTag fora do contexto de um Server Component e esperar que marque algo — a chamada precisa ocorrer durante a renderização da página que você quer marcar.

  • Expor endpoints de revalidação sem autenticação: abuse imediato de invalidação pode causar custos e problemas de cache.

  • Assumir que revalidate é ignorado pela CDN — na verdade, a CDN aplica s-maxage com base na configuração do Next, então sempre confirme os headers.

Exemplo rápido de debug: checar cache-control via curl

# Inspecione headers do CDN/App
curl -I https://www.example.com/product/123

# Procure por: Cache-Control, Age, x-next-cache (dependendo do provedor)

Interprete os campos: Age indica tempo desde a última fetch; Cache-Control mostrará s-maxage e stale-while-revalidate; se no-store aparecer, a CDN não vai armazenar.

Conclusão prática

Para rotas dinâmicas no App Router, combine: revalidate para TTLs previsíveis, createTag/revalidateTag para invalidação seletiva, e route handlers no runtime edge para ações que exigem baixa latência. Use Cache-Control para alinhar comportamento entre CDN e servidor e sempre proteja endpoints de invalidação.

Comece simples: coloque export const revalidate = 60 nas páginas mais acessadas. Depois, marque renderizações críticas com createTag e implemente endpoints de revalidação autenticados. Só então introduza edge handlers para reduzir latências críticas.

Documente os contratos de invalidação do seu sistema (quem pode invalidar, quando, e qual tag/path será afetado). Isso reduz surpresas em produção e facilita troubleshooting quando algo não atualiza como esperado.

Próximo passo prático: identifique 3 páginas/rotas com valor de negócio alto e implemente revalidate+tags nelas hoje. Teste invalidação e monitore latência e custos da CDN por duas semanas.

Assuntos: Next.js, app-router, isr, edge, caching, Performance

Leia também