Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Camada 1: Memoização de Requisição - dedup automático dentro de um único render
// Ambos os componentes chamam a mesma função, mas apenas UMA consulta ao banco de dados é executada
async function getUser(id: string) {
// O React deduplica isso automaticamente durante um único passe de renderização
return db.user.findUnique({ where: { id } });
}
// Camada 2: Cache de Dados - cache persistente para resultados de fetch()
const data = await fetch("https://api.example.com/products", {
next: { revalidate: 3600, tags: ["products"] },
});
// Camada 3: Cache de Rota Completo - HTML pré-renderizado para rotas estáticas
// Automático para páginas sem funções dinâmicas (cookies, headers, searchParams)
// Camada 4: Cache do Roteador - cache do lado do cliente para rotas visitadas
// Automático para todas as navegações, cacheado por 30s (dinâmico) ou 5min (estático)
// Invalidação
import { revalidateTag, revalidatePath } from "next/cache";
// Direcionado: invalida todos os fetches marcados com "products"
revalidateTag("products");
// Amplo: invalida uma rota específica
revalidatePath("/products");Quando usar isso: Quando você precisa controlar a atualidade dos dados versus o desempenho. Entender essas camadas evita bugs de dados desatualizados e permite o cache agressivo para páginas acessadas com frequência.
// ---- ANTES: Nenhuma estratégia de cache - cada carregamento de página atinge o banco de dados ----
// app/products/page.tsx
export const dynamic = "force-dynamic"; // Desabilita TODO o cache
export default async function ProductsPage() {
// Atinge o banco de dados em CADA requisição - 120ms por visita
const products = await db.product.findMany({
include: { category: true },
orderBy: { createdAt: "desc" },
});
// Mesma consulta executada NOVAMENTE para a contagem
const allProducts = await db.product.findMany();
const totalCount = allProducts.length;
return (
<div>
<h1>Products ({totalCount})</h1>
{products.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}
// app/products/[id]/page.tsx
export const dynamic = "force-dynamic";
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
// Atinge o banco de dados em cada visita - mesmo para o mesmo produto
const product = await db.product.findUnique({
where: { id },
include: { reviews: true, seller: true },
});
return <ProductDetail product={product} />;
}
// Resultado: 120ms por carregamento de página, sem cache, banco de dados sob carga constante
// ---- DEPOIS: Estratégia de cache em camadas - sub-50ms para páginas cacheadas ----
// lib/data/products.ts - Acesso centralizado a dados com cache
import { cache } from "react";
import { unstable_cache } from "next/cache";
// Camada 1: Memoização de Requisição - dedup dentro de um único render
// O cache() do React garante que isso só rode uma vez por passe de renderização,
// mesmo que chamado de múltiplos Server Components
export const getProductById = cache(async (id: string) => {
return db.product.findUnique({
where: { id },
include: { reviews: true, seller: true },
});
});
// Camada 2: Cache de Dados - cache persistente entre requisições
export const getProducts = unstable_cache(
async () => {
return db.product.findMany({
include: { category: true },
orderBy: { createdAt: "desc" },
});
},
["products-list"], // Chave de cache
{
revalidate: 3600, // Revalida a cada hora
tags: ["products"], // Tag para invalidação direcionada
}
);
export const getProductCount = unstable_cache(
async () => {
return db.product.count();
},
["product-count"],
{
revalidate: 3600,
tags: ["products"],
}
);
// app/products/page.tsx - Usa dados cacheados
export default async function ProductsPage() {
// Ambos usam o cache "products" - rápido após a primeira requisição
const [products, totalCount] = await Promise.all([
getProducts(),
getProductCount(),
]);
return (
<div>
<h1>Products ({totalCount})</h1>
{products.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}
// Camada 3: Cache de Rota Completo - páginas de produto pré-renderizadas no build time
export async function generateStaticParams() {
const products = await db.product.findMany({ select: { id: true } });
return products.map((p) => ({ id: p.id }));
}
// app/products/[id]/page.tsx - Estaticamente gerado + ISR
export const revalidate = 3600; // ISR: regenera a cada hora
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProductById(id);
if (!product) notFound();
return <ProductDetail product={product} />;
}
// Invalidação: Server Action após atualização do produto
// app/actions/products.ts
"use server";
import { revalidateTag, revalidatePath } from "next/cache";
export async function updateProduct(id: string, data: ProductUpdateData) {
await db.product.update({ where: { id }, data });
// Invalida o cache de dados para todos os fetches de produto
revalidateTag("products");
// Invalida o cache da rota de página de produto específica
revalidatePath(`/products/${id}`);
}
export async function deleteProduct(id: string) {
await db.product.delete({ where: { id } });
// Invalida tudo marcado com "products"
revalidateTag("products");
// Invalida a página de listagem de produtos
revalidatePath("/products");
}O que isso demonstra:
getProductById chamado de múltiplos componentes executa apenas uma vez por renderizaçãogenerateStaticParamsrevalidateTag("products") invalida todos os caches relacionados a produtos após mutaçõesfetch() ou função envolvida em cache() é chamada várias vezes durante uma única renderização do servidor, apenas uma execução ocorre. O resultado é compartilhado entre todos os locais de chamada. Isso é automático e não requer configuração.fetch() entre requisições e implantações. Quando next: { revalidate: N } é definido, a resposta cacheada é servida por N segundos. Após N segundos, a próxima requisição dispara uma revalidação em segundo plano (padrão stale-while-revalidate). Use unstable_cache para fontes de dados não-fetch, como consultas a banco de dados.cookies(), headers(), searchParams) são renderizadas estaticamente no build time. Páginas dinâmicas são renderizadas na primeira requisição e cacheadas.revalidateTag invalida todas as entradas cacheadas (Cache de Dados e Cache de Rota Completo) associadas a uma tag específica. Isso é mais direcionado do que revalidatePath, que invalida tudo em uma rota.revalidatePath invalida o Cache de Rota Completo para um caminho específico e dispara uma re-renderização na próxima requisição.Desabilitando o cache por fetch:
// Sem cache - dados sempre frescos
const data = await fetch("https://api.example.com/live-prices", {
cache: "no-store",
});
// Equivalente: configuração de segmento de rota dinâmica
export const dynamic = "force-dynamic";
export const revalidate = 0;Revalidação baseada em tempo (ISR):
// Revalidação em nível de página
export const revalidate = 60; // Revalida a cada 60 segundos
// Revalidação em nível de fetch
const data = await fetch(url, {
next: { revalidate: 300 }, // Este fetch específico é cacheado por 5 minutos
});Revalidação sob demanda em Server Actions:
"use server";
import { revalidateTag, revalidatePath } from "next/cache";
export async function publishPost(id: string) {
await db.post.update({
where: { id },
data: { published: true },
});
// Granular: invalida apenas caches relacionados ao blog
revalidateTag("blog-posts");
revalidateTag(`post-${id}`);
// Amplo: invalida toda a seção do blog
revalidatePath("/blog", "layout");
}Depuração de cache com headers:
// next.config.ts - expõe headers de status de cache
const nextConfig = {
logging: {
fetches: {
fullUrl: true, // Registra URLs de fetch completas com status de cache
},
},
};
// Verifique os headers de resposta nas Ferramentas de Desenvolvedor:
// x-nextjs-cache: HIT - servido do Cache de Rota Completo
// x-nextjs-cache: MISS - renderizado sob demanda, agora cacheado
// x-nextjs-cache: STALE - servido desatualizado, revalidando em segundo planounstable_cache aceita um genérico: unstable_cache<Product[]>(fn, keys, opts).revalidateTag e revalidatePath são tipados para aceitar parâmetros string.generateStaticParams é inferido a partir dos parâmetros do segmento de rota.cache() do React preserva a assinatura de tipo da função envolvida.cookies() ou headers() desabilitam o cache - Chamar cookies() em qualquer lugar de uma rota torna a rota inteira dinâmica, desabilitando o Cache de Rota Completo. Correção: Mova as chamadas de cookies() para o Server Component específico que as necessita, ou use middleware para verificações de autenticação.
Dados desatualizados após mutações - Atualizar dados sem chamar revalidateTag ou revalidatePath deixa as páginas cacheadas mostrando dados antigos. Correção: Sempre revalide após Server Actions que mutam dados.
Colisões de chave unstable_cache - Duas consultas diferentes com a mesma chave de cache se sobrescrevem. Correção: Use chaves de cache descritivas e únicas que incluam os parâmetros da consulta: ["products", category, sortBy].
Cache do Roteador mostrando páginas desatualizadas - O Cache do Roteador do lado do cliente pode mostrar uma versão desatualizada de uma página mesmo após a revalidação do servidor. Correção: Use router.refresh() para forçar um fetch fresco do servidor, ou aceite a janela de desatualização de 30 segundos.
revalidatePath é mais amplo do que o esperado - revalidatePath("/products") invalida a página de listagem de produtos, mas não as páginas de produtos individuais. Correção: Use revalidatePath("/products", "layout") para invalidar o layout e todas as rotas filhas, ou use revalidateTag para controle granular.
Cache de fetch em Server Components com clientes de banco de dados - O cache fetch() funciona apenas com a API Fetch. Prisma, Drizzle e outros clientes de banco de dados ignoram o Cache de Dados. Correção: Envolva consultas de banco de dados em unstable_cache para cache persistente.
Modo de desenvolvimento não cacheia - Em next dev, o cache é desabilitado por padrão para simplificar o desenvolvimento. Correção: Teste o comportamento de cache em builds de produção: npm run build && npm start.
| Abordagem | Trade-off |
|---|---|
| Cache integrado do Next.js | Integrado; modelo mental complexo com 4 camadas |
| Redis ou Upstash | Cache externo; mais controle, mais infraestrutura |
| Cache de CDN (Cloudflare, Vercel Edge) | Nível Edge; invalidação de cache é mais difícil |
| SWR stale-while-revalidate | Lado do cliente; sem cache de servidor, adiciona JS no cliente |
| ISR (Incremental Static Regeneration) | Baseado em tempo; dados desatualizados dentro da janela de revalidação |
| Revalidação sob demanda | Preciso; requer chamadas explícitas após cada mutação |
| Geração estática (SSG) | Apenas no build time; sem dados em tempo de execução, o mais rápido possível |
fetch() entre requisições e implantações.revalidateTag("products") invalida todas as entradas cacheadas (Cache de Dados + Cache de Rota Completo) com essa tag -- granular.revalidatePath("/products") invalida o Cache de Rota Completo para um caminho específico.import { cache } from "react";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// Chamado no Componente A e Componente B durante a mesma renderização:
// Apenas UMA consulta ao banco de dados é executada; ambos recebem o mesmo resultado.fetch() funciona apenas com a API Fetch.unstable_cache para cache persistente entre requisições.cookies() é uma função dinâmica que requer dados por requisição.cookies() para o Server Component específico que as necessita, ou use middleware.revalidate: N segundos, a resposta cacheada é servida (desatualizada) enquanto uma revalidação em segundo plano é executada.logging: { fetches: { fullUrl: true } } em next.config.ts para registrar URLs de fetch com status de cache.x-nextjs-cache: HIT, MISS, ou STALE.HIT = Cache de Rota Completo, MISS = renderizado sob demanda, STALE = servido desatualizado enquanto revalida.router.refresh() para forçar um fetch fresco do servidor, ou aceite a janela de desatualização.import { unstable_cache } from "next/cache";
const getProducts = unstable_cache<Product[]>(
async () => {
return db.product.findMany();
},
["products-list"],
{ revalidate: 3600, tags: ["products"] }
);
// O tipo de retorno é inferido como Promise<Product[]>cache() retorna uma função com a mesma assinatura de tipo da original.dynamic = "force-dynamic" desabilita toda a rota de todas as camadas de cache.cache: "no-store" em um fetch() específico desabilita apenas esse fetch do Cache de Dados.generateStaticParams pré-renderiza páginas de rota dinâmicas específicas no build time.revalidate, elas usam ISR para regenerar periodicamente sem um rebuild completo.["products", category, sortBy].Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥