Por Que Seu Cache do Next.js Não Funciona (E Como Resolver em 2026)
Já passou por isso desenvolvendo com Next.js? "Atualizei os dados mas continuam mostrando o mesmo..." "Configurei revalidate mas não funciona..." "Local funciona perfeito mas produção não..." Relaxa, você não é o único. O cache do App Router do Next.js é super poderoso, mas também é a parte mais confusa do framework.
Nesse artigo vamos dissecar cada camada de cache no Next.js 15 e 16, entender por que o cache se comporta de forma estranha às vezes, e te dar soluções que realmente funcionam na prática.
As 4 camadas de cache do Next.js (o básico)
Antes de começar a debugar, tem algo importante que você precisa saber: o App Router do Next.js não tem UM cache, tem 4 camadas de cache independentes. Cada uma faz algo diferente, e confundir elas é a raiz da maioria dos problemas.
1. Request Memoization (Memoização de Requisições)
Escopo: Passada de renderização única (apenas server-side)
Duração: Duração de uma única requisição
Propósito: Deduplicar fetches de dados idênticos dentro de uma única renderização
// Ambas as chamadas fetch são automaticamente deduplicadas // Apenas UMA requisição HTTP real é feita! async function ProductDetails({ id }: { id: string }) { const product = await fetch(`/api/products/${id}`); return <ProductPrice productId={id} />; } async function ProductPrice({ productId }: { productId: string }) { // Esse mesmo fetch é memoizado—sem requisição duplicada const product = await fetch(`/api/products/${productId}`); return <span>${product.price}</span>; }
A memoização de requisições acontece automaticamente para chamadas fetch com a mesma URL e opções durante uma única renderização do servidor. Esse é o comportamento nativo do React estendido pelo Next.js.
Conceito errado comum: Isso NÃO é cache persistente. Uma vez que a requisição completa, essa memoização desaparece. Ela só previne fetches duplicados dentro da mesma travessia da árvore de renderização.
2. Data Cache (Cache de Dados)
Escopo: Server-side, persistente
Duração: Até revalidação ou invalidação manual
Propósito: Cachear respostas de fetch através de requisições e deploys
// Cacheado indefinidamente (comportamento estático) const data = await fetch('https://api.example.com/data'); // Cacheado por 60 segundos const data = await fetch('https://api.example.com/data', { next: { revalidate: 60 } }); // Nunca cacheado const data = await fetch('https://api.example.com/data', { cache: 'no-store' });
O Data Cache é onde a maior parte da confusão acontece. Ele armazena a resposta crua das chamadas fetch e persiste através de:
- Múltiplas requisições de usuários
- Reinicializações do servidor (em produção com infraestrutura adequada)
- Deploys (em plataformas como Vercel)
3. Full Route Cache (Cache Completo de Rota)
Escopo: Server-side, persistente
Duração: Até revalidação
Propósito: Cachear o HTML completo e o payload RSC (React Server Component) para rotas
Quando você builda uma aplicação Next.js, rotas estáticas são pré-renderizadas no tempo de build. O Full Route Cache armazena:
- O HTML renderizado para carregamento inicial da página
- O Payload RSC para navegação do lado do cliente
// Essa página é gerada estaticamente e totalmente cacheada export default function AboutPage() { return <div>Sobre Nós</div>; } // Essa página opta por sair do Full Route Cache export const dynamic = 'force-dynamic'; export default function DashboardPage() { return <div>Dashboard: {new Date().toISOString()}</div>; }
4. Router Cache (Cache do Router, client-side)
Escopo: Client-side, por sessão
Duração: Baseado em sessão com invalidação automática
Propósito: Cachear segmentos de rota visitados no navegador para navegação instantânea voltar/avançar
Usuário visita /products → Router Cache armazena layout + página de /products Usuário navega para /products/123 → Router Cache armazena página de /products/123 → Layout de /products é reutilizado do cache Usuário clica no botão voltar → Página de /products servida instantaneamente do Router Cache
O Router Cache mudou bastante no Next.js 15 e é o que mais gera confusão. Explicamos melhor abaixo.
Como debugar quando o cache não funciona
Quando os dados não atualizam, siga esses passos:
Passo 1: Identificar Qual Camada de Cache Está Envolvida
Faça a si mesmo essas perguntas:
| Pergunta | Se Sim | Se Não |
|---|---|---|
| Os dados desatualizados aparecem imediatamente no primeiro carregamento? | Data Cache ou Full Route Cache | Router Cache (client-side) |
| O hard refresh (Cmd+Shift+R) resolve? | Router Cache | Cache server-side |
| Fazer redeploy resolve? | Full Route Cache | Configuração errada do Data Cache |
| É em desenvolvimento? | Servidor dev tem cache diferente | Verificar build de produção |
Passo 2: Verificar Sua Configuração de Fetch
O problema mais comum é entender mal os valores padrão do cache do fetch.
// ❌ Erro comum: assumir que isso é dinâmico const res = await fetch('https://api.example.com/user'); // Isso é CACHEADO POR PADRÃO no Next.js! // ✅ Correto: optar explicitamente por sair do cache const res = await fetch('https://api.example.com/user', { cache: 'no-store' }); // ✅ Ou usar revalidação baseada em tempo const res = await fetch('https://api.example.com/user', { next: { revalidate: 0 } // Revalidar em cada requisição });
Passo 3: Entender a Configuração do Segmento de Rota
A configuração do segmento de rota afeta como toda a rota é cacheada:
// app/dashboard/page.tsx // Forçar toda a rota a ser dinâmica export const dynamic = 'force-dynamic'; // Forçar geração estática (vai dar erro se funções dinâmicas forem usadas) export const dynamic = 'force-static'; // Definir revalidação para toda a rota export const revalidate = 60; // Revalidar a cada 60 segundos // Desabilitar todo o cache export const revalidate = 0; export const fetchCache = 'force-no-store';
Armadilhas que não estão na documentação
Armadilha #1: cookies() e headers() Optam Automaticamente por Sair
Quando você usa funções dinâmicas em um Server Component, toda a rota se torna dinâmica:
import { cookies } from 'next/headers'; export default async function UserPage() { // Apenas chamar cookies() torna essa rota dinâmica, // mesmo se você não usar o resultado! const cookieStore = await cookies(); // Esse fetch agora também é dinâmico const user = await fetch('/api/user'); return <div>{user.name}</div>; }
A Solução: Se você precisa de cookies mas quer cache, reestruture seu componente:
// Separar as partes dinâmicas e estáticas import { Suspense } from 'react'; export default function UserPage() { return ( <div> <StaticHeader /> {/* Essa parte está cacheada */} <Suspense fallback={<Loading />}> <DynamicUserContent /> {/* Apenas isso é dinâmico */} </Suspense> </div> ); }
Armadilha #2: O Paradoxo do searchParams
Acessar searchParams em uma página a torna dinâmica, mesmo se você não usar parâmetros de URL:
// ❌ Essa página é dinâmica mesmo se nenhum search param for passado export default function ProductsPage({ searchParams, }: { searchParams: { sort?: string }; }) { // Apenas a presença de searchParams nas props = dinâmico return <ProductGrid />; } // ✅ Melhor: só acessar searchParams quando necessário export default function ProductsPage() { return <ProductGrid />; // Estático por padrão } // Para views filtradas, usar uma rota separada ou componente cliente
Armadilha #3: Requisições POST e Data Cache
Requisições POST têm um comportamento de cache único que confunde muitos desenvolvedores:
// Requisições POST NÃO são cacheadas por padrão const res = await fetch('/api/data', { method: 'POST', body: JSON.stringify({ id: 1 }), }); // Mas se a resposta usar headers de cache, ela PODE ser cacheada! // Verifique os headers de resposta da sua API!
Armadilha #4: A Discrepância Desenvolvimento vs Produção
Essa é talvez a armadilha mais frustrante. Em desenvolvimento:
- Data Cache está desabilitado por padrão
- Full Route Cache não existe
- Hot reload às vezes limpa caches inesperadamente
# Sempre teste o comportamento do cache com um build de produção npm run build && npm start
Se seus problemas de cache só aparecem em produção, essa é a razão.
Armadilha #5: Timing do ISR e revalidate
revalidate não significa "atualizar a cada N segundos"—significa "após N segundos, a PRÓXIMA requisição vai disparar uma regeneração em background":
export const revalidate = 60; // Linha do tempo: // t=0: Página gerada, cacheada // t=30: Usuário visita → recebe versão cacheada (30s de idade) // t=65: Usuário visita → recebe versão cacheada (65s de idade), dispara regeneração em background // t=66: Novo cache pronto // t=70: Usuário visita → recebe versão fresca
O padrão stale-while-revalidate significa que usuários podem ver conteúdo desatualizado mesmo após o período de revalidação.
Soluções prontas pra usar
Receita 1: Dados de Dashboard em Tempo Real
Problema: Dashboard mostra dados desatualizados mesmo após atualizações.
// app/dashboard/page.tsx import { unstable_noStore as noStore } from 'next/cache'; export default async function Dashboard() { noStore(); // Optar por sair de todo cache para esse componente const stats = await fetchDashboardStats(); return <DashboardView stats={stats} />; } // Ou usar config de segmento de rota export const dynamic = 'force-dynamic'; export const revalidate = 0;
Receita 2: Conteúdo Específico de Usuário com Layout Compartilhado
Problema: Layout está cacheado, mas conteúdo da página precisa ser específico do usuário.
// app/dashboard/layout.tsx // Esse layout pode permanecer estático export default function DashboardLayout({ children }) { return ( <div className="dashboard-container"> <Sidebar /> {/* Cacheado */} {children} </div> ); } // app/dashboard/page.tsx import { cookies } from 'next/headers'; export default async function DashboardPage() { const cookieStore = await cookies(); const userId = cookieStore.get('userId')?.value; // Essa página é dinâmica, mas o layout ainda está cacheado const userData = await fetch(`/api/users/${userId}`, { cache: 'no-store' }); return <UserDashboard data={userData} />; }
Receita 3: Páginas de Produto E-Commerce com Atualizações de Preço
Problema: Mudanças de preço precisam ser refletidas em 5 minutos, mas descrições de produtos podem ser cacheadas por mais tempo.
// app/products/[id]/page.tsx export const revalidate = 300; // 5 minutos export default async function ProductPage({ params }) { // Detalhes do produto revalidam a cada 5 minutos const product = await fetch(`/api/products/${params.id}`, { next: { revalidate: 300 } }); // Estoque deve ser em tempo real const inventory = await fetch(`/api/inventory/${params.id}`, { cache: 'no-store' }); return <ProductView product={product} inventory={inventory} />; }
Receita 4: Blog com Revalidação On-Demand
Problema: Posts do blog devem ser cacheados, mas revalidar quando o conteúdo é atualizado no CMS.
// app/blog/[slug]/page.tsx export const revalidate = false; // Cachear indefinidamente export default async function BlogPost({ params }) { const post = await fetch(`/api/posts/${params.slug}`, { next: { tags: [`post-${params.slug}`] } }); return <Article post={post} />; } // app/api/revalidate/route.ts import { revalidateTag } from 'next/cache'; import { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { const { slug, secret } = await request.json(); if (secret !== process.env.REVALIDATION_SECRET) { return Response.json({ error: 'Secret inválido' }, { status: 401 }); } revalidateTag(`post-${slug}`); return Response.json({ revalidated: true }); }
Receita 5: Corrigindo Problemas do Router Cache (Next.js 15)
Problema: Navegação client-side mostra dados desatualizados.
Desde o Next.js 15, o comportamento do Router Cache mudou significativamente. Páginas dinâmicas não são mais cacheadas no cliente por padrão, mas páginas estáticas ainda são. O Next.js 16 continua essa abordagem com refinamentos adicionais.
// Para rotas dinâmicas que precisam de dados frescos em cada navegação: import { useRouter } from 'next/navigation'; function RefreshButton() { const router = useRouter(); const handleRefresh = () => { router.refresh(); // Invalida o Router Cache para a rota atual }; return <button onClick={handleRefresh}>Atualizar Dados</button>; }
Para cache busting mais agressivo:
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const response = NextResponse.next(); // Prevenir Router Cache para rotas específicas if (request.nextUrl.pathname.startsWith('/dashboard')) { response.headers.set( 'Cache-Control', 'no-store, must-revalidate' ); } return response; }
Padrões Avançados: Estratégias de Cache Tags
Cache tags permitem criar relacionamentos entre dados cacheados e invalidá-los com precisão:
// lib/data.ts export async function getProducts(category: string) { return fetch(`/api/products?category=${category}`, { next: { tags: [ 'products', // Todos os produtos `category-${category}`, // Categoria específica ] } }); } export async function getProductById(id: string) { return fetch(`/api/products/${id}`, { next: { tags: [ 'products', `product-${id}`, ] } }); } // Quando um único produto é atualizado: revalidateTag(`product-${updatedProductId}`); // Quando categoria é reorganizada: revalidateTag(`category-${categoryName}`); // Opção nuclear - todos os produtos: revalidateTag('products');
Ferramentas e Técnicas de Debug
1. Inspeção de Headers de Cache
// Adicionar headers de debug de cache em desenvolvimento // next.config.js module.exports = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'x-next-cache-status', value: process.env.NODE_ENV === 'development' ? 'disabled' : 'enabled', }, ], }, ]; }, };
2. Logging do Comportamento do Cache
// lib/fetch-with-logging.ts export async function fetchWithCacheLogging(url: string, options?: RequestInit) { const startTime = performance.now(); const response = await fetch(url, options); console.log({ url, cacheMode: options?.cache ?? 'default', revalidate: (options as any)?.next?.revalidate ?? 'não definido', responseTime: `${(performance.now() - startTime).toFixed(2)}ms`, fromCache: response.headers.get('x-cache') === 'HIT', }); return response; }
3. Indicador Visual de Cache
// components/CacheDebugBadge.tsx import { unstable_cache } from 'next/cache'; export function CacheDebugBadge() { if (process.env.NODE_ENV !== 'development') return null; const renderTime = new Date().toISOString(); return ( <div className="fixed bottom-4 right-4 bg-black text-white p-2 text-xs rounded"> Renderizado: {renderTime} </div> ); }
Mudanças de Cache no Next.js 15/16: O Que Há de Novo?
Next.js 15 introduziu várias mudanças importantes nos padrões de cache, e Next.js 16 (lançado em outubro de 2025) revoluciona o cache com uma abordagem completamente nova.
Mudanças no Next.js 15 (Ainda Relevantes)
1. Requisições fetch Não São Mais Cacheadas por Padrão
// Next.js 14 e anteriores: cacheado por padrão // Next.js 15+: NÃO cacheado por padrão // Para restaurar o comportamento antigo: const data = await fetch(url, { cache: 'force-cache' });
2. Route Handlers São Dinâmicos por Padrão
// Next.js 14 e anteriores: handlers GET eram cacheados // Next.js 15+: todos os handlers são dinâmicos por padrão // Para tornar um route handler estático: export const dynamic = 'force-static'; export async function GET() { return Response.json({ time: new Date().toISOString() }); }
3. Mudanças no Router Cache do Cliente
// Next.js 14 e anteriores: Páginas cacheadas por 30 segundos (dinâmico) ou 5 minutos (estático) // Next.js 15+: Páginas dinâmicas têm 0 tempo de staleness // Para configurar tempo de staleness: // next.config.js module.exports = { experimental: { staleTimes: { dynamic: 30, // segundos static: 180, // segundos }, }, };
Next.js 16: A Revolução do "use cache"
Next.js 16 introduz Cache Components com a diretiva "use cache"—a mudança de cache mais significativa desde que o App Router foi introduzido. Isso completa a visão do Partial Pre-Rendering (PPR).
A Mudança de Paradigma: De Implícito para Explícito
No Next.js 16, todo código dinâmico é executado no momento da requisição por padrão. O cache agora é completamente opt-in:
// Next.js 16: Esta página é dinâmica por padrão export default async function ProductPage({ params }) { const product = await fetch(`/api/products/${params.id}`); return <ProductView product={product} />; } // Para cachear, você DEVE usar "use cache" explicitamente "use cache" export default async function ProductPage({ params }) { const product = await fetch(`/api/products/${params.id}`); return <ProductView product={product} />; }
Usando "use cache" em Diferentes Níveis
// Cachear uma página inteira "use cache" export default async function BlogPostPage({ params }) { const post = await fetchPost(params.slug); return <Article post={post} />; } // Cachear um componente específico async function CachedSidebar() { "use cache" const categories = await fetchCategories(); return <Sidebar categories={categories} />; } // Cachear uma função async function getExpensiveData(id: string) { "use cache" // Este resultado será cacheado return await computeExpensiveData(id); }
Combinando com Cache Tags
"use cache" import { cacheTag } from 'next/cache'; export default async function ProductPage({ params }) { cacheTag(`product-${params.id}`); const product = await fetch(`/api/products/${params.id}`); return <ProductView product={product} />; } // Invalidar com revalidateTag import { revalidateTag } from 'next/cache'; revalidateTag(`product-${params.id}`);
Novo: cacheLife para Controle de Expiração
"use cache" import { cacheLife } from 'next/cache'; export default async function DashboardStats() { cacheLife('minutes'); // Perfis predefinidos: 'seconds', 'minutes', 'hours', 'days', 'weeks', 'max' const stats = await fetchStats(); return <StatsView stats={stats} />; } // Ou com valores customizados cacheLife({ stale: 60, // Servir stale por 60 segundos revalidate: 300, // Revalidar após 5 minutos expire: 3600, // Expirar após 1 hora });
Cache do Sistema de Arquivos Turbopack (Beta)
Next.js 16 faz do Turbopack o bundler padrão com cache do sistema de arquivos:
// next.config.js module.exports = { experimental: { turbo: { // Cache do sistema de arquivos para rebuilds mais rápidos persistentCaching: true, }, }, };
Isso melhora dramaticamente os tempos de startup e compilação ao armazenar artefatos do compilador no disco entre execuções.
Implicações de Performance: Quando Cachear O Quê
Entender cache não é apenas evitar bugs—é também otimizar performance:
| Tipo de Conteúdo | Estratégia Recomendada | Razão |
|---|---|---|
| Páginas de marketing | revalidate: 3600 ou estático em build | Muda raramente, maximiza hits de CDN |
| Listagens de e-commerce | revalidate: 60 + on-demand | Equilíbrio entre frescor e performance |
| Dashboards de usuário | dynamic = 'force-dynamic' | Específico do usuário, precisa de dados frescos |
| Rotas de API (públicas) | revalidate: 300 + cache tags | Reduzir carga do backend |
| Rotas de API (auth) | cache: 'no-store' | Segurança e frescor |
| Dados em tempo real | Fetching client-side | Cache do servidor inadequado |
Mensagens de Erro Comuns e Soluções
"DYNAMIC_SERVER_USAGE"
Error: Dynamic server usage: cookies
Solução: Você está usando funções dinâmicas em uma rota de exports estáticos. Você pode:
- Remover a função dinâmica
- Adicionar
export const dynamic = 'force-dynamic' - Mover lógica dinâmica para componentes cliente
"Invariant: static generation store missing"
Isso geralmente indica tentativa de usar APIs dinâmicas durante o build:
// ❌ Problema export async function generateStaticParams() { const cookieStore = await cookies(); // Não pode usar aqui! return []; } // ✅ Solução: só usar dados estáticos em generateStaticParams export async function generateStaticParams() { const products = await fetch('/api/products', { cache: 'force-cache' }).then(r => r.json()); return products.map(p => ({ id: p.id })); }
Cache Não Invalida na Vercel
// Certifique-se de estar usando a API de revalidação correta import { revalidatePath, revalidateTag } from 'next/cache'; // revalidatePath invalida todos os dados para um path revalidatePath('/products'); // revalidateTag é mais cirúrgico revalidateTag('products-list'); // Ambos requerem que a requisição se origine do mesmo deploy // Use Webhook do CMS → API Route → função revalidate
Conclusão: Modelo Mental para Cache do Next.js
Depois de digerir tudo isso, aqui está o modelo mental para levar adiante:
-
Por padrão dinâmico no Next.js 15/16. Comece sem cache e adicione onde for benéfico, ao invés de debugar hits de cache inesperados.
-
Separe estático e dinâmico. Use limites Suspense e composição de componentes para maximizar conteúdo cacheável.
-
Cache no nível certo. Não use Full Route Cache quando Data Cache para fetches específicos seria suficiente.
-
Sempre teste com builds de produção. O servidor de desenvolvimento mente sobre comportamento de cache.
-
Use cache tags para precisão. São mais manuteníveis que invalidação baseada em path para apps complexas.
-
Monitore comportamento de cache em produção. Adicione logging, use Vercel Analytics, ou implemente headers de cache customizados.
Cache no Next.js é complexo porque está resolvendo um problema complexo: entregar conteúdo rápido e fresco em escala. Uma vez que você entenda as quatro camadas e suas interações, você poderá construir aplicações que são incrivelmente rápidas e confiavelmente atualizadas.
A chave não é lutar contra o cache, mas trabalhar com ele—configurando cada camada apropriadamente para seu caso de uso específico e entendendo que às vezes, a melhor configuração de cache é não ter cache nenhum.
Explore ferramentas relacionadas
Experimente estas ferramentas gratuitas do Pockit