Cache Components no Next.js: o que muda no PPR
A diretiva use cache, com cacheLife e cacheTag, dá controle granular sobre o que vira estático no Next.js — e redesenha como pensamos PPR. Análise prática para times que rodam em produção.
Resposta atômica: Cache Components é a evolução do modelo de cache do Next.js. A diretiva
use cache, junto decacheLifeecacheTag, permite cachear componentes e funções de servidor com granularidade fina — coisa que o Full Route Cache e o Data Cache, sozinhos, não cobriam bem. PPR continua existindo; o que mudou foi o que cada modelo resolve melhor.
Por que existe
Versões anteriores do Next.js misturavam três coisas no mesmo conceito de "cache": resultado de fetch, página inteira e dados em runtime. Times grandes batiam em um teto previsível — querer cachear parte de um componente sem cachear a rota inteira virava ginástica com unstable_cache, revalidate nos exports e segmentação manual de Server Components.
Cache Components resolve o problema explicitando três coisas:
- O que cachear — uma função, um Server Component, um trecho.
- Por quanto tempo — perfis nomeados de TTL (
cacheLife). - Como invalidar — tags arbitrárias atreladas a entradas (
cacheTag).
O modelo mental
// app/lib/cases.ts
import { cacheLife, cacheTag } from 'next/cache';
export async function getCases(category: string) {
'use cache';
cacheLife('hours');
cacheTag('cases', `cases:${category}`);
return db.cases.findMany({ where: { category } });
}
Três decisões aparecem no código, e somente onde elas importam:
'use cache'marca a função como elegível ao cache. Os inputs (category) viram parte da chave automaticamente.cacheLife('hours')aplica um perfil de TTL. Os perfis padrão (seconds,minutes,hours,days,weeks,max) cobrem 95% dos casos. Perfis customizados existem para o resto.cacheTag(...)declara o que esta entrada representa. Mais tarde, quando um case for atualizado,revalidateTag('cases:fintech')derruba só o necessário.
A entrada cacheada continua sendo recomputada uma vez por chave mesmo sob concorrência — atributo herdado do design subjacente da Fluid Compute, que reusa instâncias entre requests.
Onde isso muda PPR
PPR (Partial Prerendering) responde a uma pergunta diferente: "como sirvo o shell instantaneamente e streamo só as partes dinâmicas?". O shell vira HTML estático no build; o resto chega via Suspense durante o request.
Cache Components opera em uma granularidade abaixo. Você pode ter:
- PPR ligado na rota — shell estático, partes dinâmicas streamadas;
- dentro das partes dinâmicas, funções marcadas com
use cachecujo resultado é reaproveitado entre requests.
Em outras palavras, PPR define onde o request bloqueia. Cache Components define o que dentro do request não precisa recomputar.
Em rotas puramente estáticas, Cache Components reduz o tempo de build e o footprint de memória — em vez de gerar HTML pré-renderizado para mil combinações de filtros, você cacheia as funções subjacentes e a página continua estática-do-ponto-de-vista-do-cliente.
Quando usar cada perfil de cacheLife
Não tem fórmula universal, mas alguns critérios pragmáticos:
| Perfil | Use quando |
|---|---|
seconds | Conteúdo quase-realtime, dados de mercado, contadores |
minutes | Feeds, listagens de blog, status de assinatura |
hours | Cases, FAQ, páginas editoriais com revisão diária |
days | Documentação, glossário, páginas institucionais |
weeks | Conteúdo arquival, livros, referência |
max | Imutável por design (artigos versionados, snapshots) |
Perfis custom (cacheLife({ revalidate, expire, stale })) entram quando o time precisa diferenciar quando o cache fica obsoleto de quando ele expira de vez. Por exemplo, stale = 5 minutos permite servir uma resposta levemente velha enquanto a próxima é recomputada em background.
Invalidação por tag, na prática
A regra é simples: a tag descreve o que a entrada representa, não onde ela é usada.
// app/lib/cases.ts
export async function getCase(slug: string) {
'use cache';
cacheLife('hours');
cacheTag(`case:${slug}`);
return db.cases.findUnique({ where: { slug } });
}
// app/actions/update-case.ts
'use server';
import { revalidateTag } from 'next/cache';
export async function updateCase(slug: string, data: CaseUpdate) {
await db.cases.update({ where: { slug }, data });
revalidateTag(`case:${slug}`);
}
Note o ponto fundamental: a tag carrega a identidade do recurso. Quando o case fintech-x muda, derrubo só ele — não a listagem inteira, não a rota.
Para invalidações que dependem de uma transição de UI, prefira updateTag em vez de revalidateTag. O efeito final é o mesmo; a diferença é que updateTag se integra à transição do React, evitando que o usuário veja conteúdo pré e pós-mudança piscando.
Erros comuns
1. Cachear dados sensíveis sem isolamento. Se a função recebe userId como input, a chave inclui esse userId — ótimo. Se a função lê cookies() ou headers() dentro do corpo cacheado, a chave não muda por usuário. Em compensação, 'use cache' proíbe leitura de APIs request-bound; quando você tenta, o build falha. Isso é proposital — segurança por construção.
2. Tags genéricas demais. cacheTag('all') em tudo. Aí, qualquer mudança derruba tudo, e o cache vira teatro. Tags úteis são compostas: cases:fintech, case:fintech-x, user:42:plan.
3. Esquecer que perfis de TTL têm semântica. cacheLife('seconds') não significa "5 segundos exatos". Significa "revalidação rápida"; o tempo exato depende da configuração ativa. Para casos em que o número importa, use perfis custom explícitos.
4. Misturar com 'use client'. Cache Components vive no servidor. Em Client Components, o cache que importa é o do navegador, do React Router (cache de Server Components) e o que vier no fetch.
Migrando código existente
Para quem vinha de unstable_cache ou de export const revalidate = N:
- Funções com
unstable_cache(fn, keys, opts)viram funções com'use cache'+cacheLife+cacheTag. A chave passa a ser inferida dos inputs. revalidateno nível do segmento ainda funciona, mas é mais grosseiro. Em rotas onde só parte dos dados precisa revalidar com frequência diferente do resto, migrar parause cachegranular dá melhor ROI.- ISR convencional continua válido para casos simples. Cache Components brilha quando há filtros, parâmetros ou combinações que multiplicariam o build.
Custos que ninguém menciona
Caches têm dois custos esquecidos: memória e erros raros que ficam grudados.
- O storage do cache (KV interno, store de objeto) tem custo. Em produção, monitore o hit rate por tag e expurgue tags obsoletas em deploys.
- Se uma exceção transiente entra na função antes do
return, a falha não é cacheada — Next.js distingue erro de resultado. Mas se a função "consegue" retornar um valor degradado (por exemplo, lista vazia em vez de lançar), esse valor degradado será cacheado. Trate erros explicitamente:throwse a fonte está fora; só retorne vazio quando isso for o resultado correto.
Quando não usar
Existe um anti-padrão clássico que vale gritar: não cacheie a fronteira de autenticação. Se a função tem decisão dependente do usuário (planos, papéis, escopos), ou ela aceita esses dados como input explícito (e a chave reflete isso), ou ela não é candidata a 'use cache'. Mesma regra vale para qualquer cálculo que dependa de tempo absoluto, locale dinâmico ou conteúdo PII.
O que isso significa para o seu projeto
Se você está em Next.js com PPR experimental, três decisões mudam:
- Onde vivia
unstable_cache— candidato direto a refatoração para'use cache'+ tags. Ganho imediato em legibilidade e granularidade de invalidação. - Onde a rota tinha
revalidategrosso — vale identificar quais funções dentro daquela rota realmente precisam revalidar nesse intervalo. Mover o TTL para a função certa preserva entregas instantâneas no resto. - Onde havia geração estática extensa via
generateStaticParams— Cache Components pode mover parte daquele custo do build para o primeiro request quente, reduzindo tempo de deploy sem regredir cold start (Fluid Compute reusa instâncias).
O modelo recompensa quem pensa em identidade de recurso (o que a tag descreve) e horizonte de obsolescência (o que cacheLife permite). Quem cacheia tudo do mesmo jeito perde rápido para quem fragmenta corretamente.
Próximo passo
Antes de refatorar, mapeie o sistema atual: liste funções, anote frequência de leitura, anote impacto de invalidação, e classifique cada uma em uma das categorias acima. Em 90% dos casos, a refatoração entrega ganhos mensuráveis sem alterar a UI — e o time fica com um modelo mental que sobrevive às próximas versões do framework.
Fontes citadas
- Next.js — directives: use cache · acessado em 2026-05-18
- Next.js — functions: cacheLife · acessado em 2026-05-18
- Next.js — functions: cacheTag · acessado em 2026-05-18
- Vercel — Fluid Compute · acessado em 2026-05-18
Leia também