Cache no Next.js: entenda antes de brigar com ele

Guia prático sobre os diferentes tipos de cache do Next.js (App Router). Entenda Data Cache, Full Route Cache e Router Cache, quando cada um entra em ação e como forçar a atualização de dados quando precisar.


Tem uma classe de bug que todo mundo que usa o App Router do Next já enfrentou: você muda um dado no banco, atualiza a página, e o valor antigo continua lá, teimoso. A reação natural é achar que o Next quebrou. Na maioria das vezes não quebrou nada, só tem uma camada de cache no meio do caminho que você ainda não conhece direito. Vou destrinchar isso aqui.

Cache não é uma coisa só

O primeiro erro é tratar "cache do Next" como se fosse uma única chave liga/desliga. Na prática existem camadas diferentes, cada uma resolvendo um problema diferente:

  • Data Cache: guarda o resultado de um fetch no servidor, entre requisições e até entre deploys.
  • Full Route Cache: guarda o HTML e o payload de uma rota inteira, gerado em build ou na primeira visita.
  • Router Cache: roda no navegador, guarda o resultado de navegações recentes pra deixar a troca de página instantânea.

Cada uma tem uma vida útil diferente e uma forma diferente de ser invalidada. O bug do "dado desatualizado" quase sempre mora numa dessas três, então vale entender uma por uma.

Data Cache: o cache do fetch

Quando você usa fetch num Server Component, o Next intercepta essa chamada e, por padrão, guarda o resultado. Da próxima vez que aquele mesmo fetch rodar, com os mesmos parâmetros, ele devolve o valor guardado em vez de bater na API ou no banco de novo.

// isso é cacheado por padrão
const res = await fetch("https://api.exemplo.com/produtos");

Se você não quer isso, dá pra desligar por chamada:

const res = await fetch("https://api.exemplo.com/produtos", {
  cache: "no-store",
});

Ou pedir pra revalidar depois de um tempo, em segundos:

const res = await fetch("https://api.exemplo.com/produtos", {
  next: { revalidate: 60 },
});

Com revalidate: 60, o Next continua servindo o valor guardado, mas depois de 60 segundos a próxima requisição dispara uma atualização em segundo plano. É o famoso stale-while-revalidate: ninguém trava esperando o dado novo, mas o dado novo chega logo depois.

Full Route Cache: a rota inteira guardada

Além do dado, o Next também pode guardar a rota renderizada como um todo. Isso acontece quando a rota é considerada estática, ou seja, não depende de nada dinâmico como cookies, headers ou parâmetros de busca lidos direto na função.

Qualquer coisa que torne a rota dinâmica (usar cookies(), headers(), ou passar cache: "no-store" num fetch dela) tira a rota desse cache automaticamente. Isso costuma surpreender quem espera uma página atualizar e ela simplesmente não atualiza: geralmente é porque o Next decidiu, na build, que aquela rota era estática o suficiente pra virar HTML fixo.

Revalidando sob demanda

Esperar o tempo passar nem sempre é uma opção. Se o usuário acabou de criar um post, você quer que a lista apareça atualizada na hora, não daqui a 60 segundos. Pra isso existem duas funções, chamadas depois de uma mutação:

import { revalidatePath, revalidateTag } from "next/cache";
 
// invalida uma rota específica
revalidatePath("/produtos");
 
// invalida todo fetch marcado com essa tag
revalidateTag("produtos");

revalidateTag costuma ser mais flexível, porque você marca o fetch com uma tag na hora de buscar o dado:

const res = await fetch("https://api.exemplo.com/produtos", {
  next: { tags: ["produtos"] },
});

E aí, de qualquer lugar do sistema (uma Server Action de criar produto, por exemplo), você chama revalidateTag("produtos") e todo fetch marcado com essa tag é invalidado, não importa em qual página ele estava.

Router Cache: o cache do lado do cliente

Esse é o mais fácil de esquecer porque não é visível no servidor. Quando você navega entre páginas usando <Link>, o Next guarda no navegador o resultado de rotas já visitadas, pra próxima navegação ser instantânea, sem esperar o servidor de novo.

O problema é que esse cache pode segurar uma versão antiga da página por um tempo, mesmo depois de você ter chamado revalidatePath no servidor. Se você mudou um dado e a navegação client-side ainda mostra o valor velho, geralmente é esse cache específico. Um router.refresh() no cliente força a busca de novo, ignorando essa camada.

Na prática

Quando algo não atualiza como deveria, a pergunta certa não é "o cache do Next tá bugado", é "qual das três camadas ainda tá seguranda esse valor". Data Cache resolve com revalidate ou revalidateTag. Full Route Cache resolve tornando a rota dinâmica ou revalidando. Router Cache resolve com router.refresh() ou navegando de um jeito que force a busca de novo.

Não é sobre desligar cache no projeto inteiro só porque deu problema uma vez. É sobre usar cada camada pro que ela resolve bem e invalidar de forma cirúrgica quando o dado muda. Isso é a diferença entre um site rápido e um site rápido que mostra informação errada.