Pular para o conteúdo
COLMEIA.digital
Engenharia de software7 min de leitura

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 de cacheLife e cacheTag, 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:

  1. O que cachear — uma função, um Server Component, um trecho.
  2. Por quanto tempo — perfis nomeados de TTL (cacheLife).
  3. 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 cache cujo 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:

PerfilUse quando
secondsConteúdo quase-realtime, dados de mercado, contadores
minutesFeeds, listagens de blog, status de assinatura
hoursCases, FAQ, páginas editoriais com revisão diária
daysDocumentação, glossário, páginas institucionais
weeksConteúdo arquival, livros, referência
maxImutá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.
  • revalidate no 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 para use cache granular 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: throw se 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:

  1. Onde vivia unstable_cache — candidato direto a refatoração para 'use cache' + tags. Ganho imediato em legibilidade e granularidade de invalidação.
  2. Onde a rota tinha revalidate grosso — 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.
  3. 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

  1. Next.js — directives: use cache · acessado em 2026-05-18
  2. Next.js — functions: cacheLife · acessado em 2026-05-18
  3. Next.js — functions: cacheTag · acessado em 2026-05-18
  4. Vercel — Fluid Compute · acessado em 2026-05-18

Leia também

  1. Engenharia de software

    Bun 1.3 em produção: cron nativo, test runner paralelo e 17x menos memória

  2. Engenharia de software

    Next.js 16.2: o framework virou agent-ready

  3. Arquitetura & escalabilidade

    PostgreSQL 18: I/O assíncrono, UUIDv7 e o que muda em SaaS