Next.js: Cache Components & use cache
Última atualização: 2026-08-26
use cacheé a API mais transformadora no Next.js 16 — ela eleva o cache de um "efeito colateral da busca de dados" para um "cidadão de primeira classe no nível do componente".
1. O Que Você Vai Aprender
- A Diretiva
use cache()e o Conceito de Cache de Conteúdo cacheTag()Etiquetando conteúdo em cachecacheLife()Configurando o tempo de expiração do cacherevalidateTag()Expiração de Cache Sob Demanda- A diferença entre cache implícito de fetch e cache explícito com
use cache
2. Uma História Real de um Arquiteto de Sistemas
(1) Ponto de Dor: Quatro chamadas de API idênticas na mesma página
Ao revisar o Dashboard do TaskFlow, Charlie notou que os quatro componentes na página — <UserAvatar>, <UserGreeting>, <UserStats> e <UserNotifications> — estavam cada um chamando fetch('/api/user'). Embora o cache para a mesma URL funcionasse bem, a função de consulta ao banco de dados getUserFromDB() era chamada quatro vezes. O cache do fetch só se aplica a requisições HTTP; é completamente ineficaz contra chamadas internas de funções do servidor.
| Problema | Dados |
|---|---|
| Chamadas ao banco de dados no nível da página | 4 consultas idênticas |
| Tempo por consulta | 200 ms |
| Tempo Adicional Total | 600 ms Desperdiçados |
| Desperdício de QPS do Banco de Dados | 4x |
(2) Solução com use cache
Envolva a função em
use cache()— isso armazena em cache quaisquer chamadas internas de função, incluindo consultas ao banco de dados, cálculos e leituras de arquivos.
// app/dashboard/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
async function getUserData() {
'use cache'
cacheTag('user-data')
cacheLife({ stale: 300, revalidate: 600 })
const user = await db.user.findUnique({ where: { id: 1 } }) // Executa apenas uma vez
return user
}
export default async function DashboardPage() {
const user = await getUserData() // A primeira chamada executa uma consulta ao banco de dados
// Chamadas subsequentes retornarão o resultado em cache diretamente.
return (
<div>
<UserAvatar user={user} />
<UserGreeting user={user} />
<UserStats user={user} />
<UserNotifications user={user} />
</div>
)
}
(3) Resultados
| Dimensão | Modelo Tradicional | use cache |
|---|---|---|
| Número de consultas ao banco de dados | 4 | 1 |
| Atraso adicional | 600 ms | 0 ms |
| Granularidade do Cache | Nível de URL | Nível de Função/Componente |
| Cache de lógica personalizada | ❌ Não suportado (apenas HTTP fetch) | ✅ Suporta qualquer código |
3. A Diretiva use cache() e o Cache de Conteúdo
use cache é uma diretiva em nível de função — adicione 'use cache' no topo de uma função para indicar que o resultado da função deve ser cacheado. O conteúdo em cache é chamado de Cache de Conteúdo (Content Cache), uma nova camada de cache introduzida no Next.js 16 que é independente do Cache de Dados (fetch cache) tradicional.
graph TB
subgraph "Sistema de Cache do Next.js 16"
A[Memoization de Requisição<br/>Memória no nível da requisição]
B[Cache de Dados<br/>Cache do fetch]
C[Cache de Conteúdo<br/>use cache]
D[Cache de Rota Completa<br/>Cache de Rota Inteira]
end
A --> E[Dentro da mesma requisição<br/>O mesmo fetch Remove duplicatas]
B --> F[Persistência Entre Requisições<br/>force-cache/no-store]
C --> G[Cache dos Resultados de Funções Arbitrárias<br/>Controle via tag + life]
D --> H[Cache HTML no nível da página]
style C fill:#d4edda
| Nível de Cache | Escopo | Gatilho | Ciclo de Vida |
|---|---|---|---|
| Memoization de Requisição | Requisição Única | Automático (Mesma URL) | Fim da Requisição |
| Cache de Dados | Entre Requisições | fetch(options) |
Dependente da Configuração |
| Cache de Conteúdo | Entre Requisições | Diretiva 'use cache' |
cacheLife + cacheTag |
| Cache de Rota Completa | Entre requisições | Build / Runtime | Revalidate / Sob demanda |
(1) Sintaxe Básica
// app/cache-demo/actions.ts
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
export async function getExpensiveData(id: string) {
'use cache'
cacheTag('expensive', `id-${id}`) // Etiquetagem
cacheLife({ stale: 60, revalidate: 300 }) // Retorna stale em 60 segundos, Tenta novamente em 300 segundos
// Todas as operações dentro desta função são cacheadas.
const result = await db.query(...)
return result
}
(2) Configuração do cacheLife
| Parâmetro | Tipo | Descrição | Exemplo |
|---|---|---|---|
stale |
number (segundos) | Retorna o resultado diretamente enquanto o cache é válido; não dispara atualização em segundo plano | { stale: 60 } |
revalidate |
number (segundos) | Reexecuta a função após este tempo ter decorrido | { revalidate: 3600 } |
expire |
number (segundos) | O cache expira absolutamente; força nova busca | { expire: 86400 } |
▶ Exemplo: Uso Básico do use cache (Dificuldade ⭐)
Saída:
Renderiza a interface do componente getExpensiveData.
// app/cache-basic/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
async function getServerTime() {
'use cache'
cacheTag('server-time')
cacheLife({ stale: 10, revalidate: 30 })
// Simula operação demorada
await new Promise(resolve => setTimeout(resolve, 1000))
return { time: new Date().toISOString(), server: process.env.HOSTNAME ?? 'local' }
}
export default async function CacheBasicPage() {
const [t1, t2, t3] = await Promise.all([
getServerTime(),
getServerTime(),
getServerTime(),
])
return (
<div>
<h1>use cache — Básico</h1>
<p>Chamada 1: {t1.time}</p>
<p>Chamada 2: {t2.time}</p>
<p>Chamada 3: {t3.time}</p>
<p><em>Todas as três chamadas retornaram o mesmo resultado em cache (sem computação duplicada)</em></p>
</div>
)
}
Saída:
Chamada 1: 2026-07-06T10:00:00.000Z
Chamada 2: 2026-07-06T10:00:00.000Z ← Mesmo timestamp (cache hit)
Chamada 3: 2026-07-06T10:00:00.000Z ← Mesmo timestamp (cache hit)
Saída:
Chamada 1: 2026-07-06T10:00:00.000Z
Chamada 2: 2026-07-06T10:00:00.000Z ← Milissegundos de sobreposição de cache
Chamada 3: 2026-07-06T10:00:00.000Z
4. Uso Prático de cacheTag e cacheLife
cacheTag() Rotula o conteúdo em cache, cacheLife() controla a política de expiração. Combinado com revalidateTag(), isso permite controle refinado de cache.
(1) Estratégia de Etiquetagem e Categorização
graph TB
A[Dados da Aplicação] --> B[Dados do Usuário]
A --> C[Dados do Produto]
A --> D[Dados do Pedido]
B --> E[tag: user-profile]
B --> F[tag: user-settings]
C --> G[tag: products]
C --> H[tag: product-{id}]
D --> I[tag: orders]
D --> J[tag: order-{id}]
style A fill:#cce5ff
| Política | Exemplos de Tag | Operações de Invalidação | Escopo |
|---|---|---|---|
| Granularidade fina | product-42 |
revalidateTag('product-42') |
Apenas um produto |
| Granularidade média | products |
revalidateTag('products') |
Todos os Produtos |
| Granularidade grossa | catalog |
revalidateTag('catalog') |
Catálogo inteiro |
(2) Tags Dinâmicas
// app/products/[id]/page.tsx
import { unstable_cacheTag as cacheTag } from 'next/cache'
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id)
return <ProductView product={product} />
}
async function getProduct(id: string) {
'use cache'
cacheTag('products', `product-${id}`) // Tags dinâmicas baseadas em parâmetros
return fetch(`https://api.example.com/products/${id}`).then(r => r.json())
}
▶ Exemplo: Estratégia de Cache em Camadas (Dificuldade: ⭐⭐)
Saída:
Obtém dados e renderiza o resultado.
// app/cache-strategy/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
async function getUserProfile(userId: number) {
'use cache'
cacheTag('users', `user-${userId}`)
cacheLife({ stale: 120, revalidate: 600 }) // 2 minutos stale, 10 minutos revalidate
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then(r => r.json())
}
async function getUserPosts(userId: number) {
'use cache'
cacheTag('posts', `user-posts-${userId}`)
cacheLife({ stale: 60, revalidate: 300 })
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`).then(r => r.json())
}
export default async function CacheStrategyPage() {
const [profile, posts] = await Promise.all([
getUserProfile(1),
getUserPosts(1),
])
return (
<div>
<h1>{profile.name}</h1>
<p>Email: {profile.email}</p>
<h2>Posts ({posts.length})</h2>
<ul>{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>
</div>
)
}
Saída:
Obtém dados no lado do servidor e renderiza uma lista de itens a partir dos dados.
▶ Exemplo: Invalidando Cache por tag na Server Action (Dificuldade: ⭐⭐)
Saída:
O componente renderiza a UI descrita no navegador.
// app/cache-invalidation/page.tsx
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache'
async function getTaskList() {
'use cache'
cacheTag('tasks')
return fetch('https://jsonplaceholder.typicode.com/todos?_limit=5').then(r => r.json())
}
export default async function CacheInvalidationPage() {
const tasks = await getTaskList()
return (
<div>
<h1>Lista de Tarefas</h1>
<ul>{tasks.map((t: any) => <li key={t.id}>{t.title}</li>)}</ul>
<form action={async () => {
'use server'
revalidateTag('tasks') // Clique no botão para atualizar o cache da lista de tarefas imediatamente
}}>
<button type="submit">Atualizar Tarefas</button>
</form>
</div>
)
}
Saída:
Um formulário com campos de entrada e um botão de envio.
Ao enviar, a server action processa os dados e invalida as tags de cache relevantes.
Texto visível: Lista de Tarefas
5. Cache Implícito do fetch vs. Cache Explícito com use cache
| Dimensão | Cache implícito do fetch (Data Cache) | Cache explícito use cache (Content Cache) |
|---|---|---|
| Método de Disparo | fetch(url) Automático |
Corpo da Função com Instrução 'use cache' |
| Conteúdo Cacheado | Resposta HTTP | Resultado retornado por qualquer função |
| Casos de Uso | Chamadas de API, requisições HTTP | Consultas ao banco de dados, cálculos complexos, leitura de arquivos |
| Sistema de Tags | next: { tags: [...] } |
Função cacheTag() |
| Controle de Tempo | next: { revalidate: N } |
Função cacheLife() |
| Modo de Falha | revalidateTag() / revalidatePath() |
revalidateTag() (mesmo rótulo) |
(1) Quando usar fetch cache e quando usar use cache?
| Cenário | Método de Cache Recomendado | Motivo |
|---|---|---|
| Chamar APIs Externas | Data Cache (fetch) |
Suporte Nativo para Semântica HTTP |
| Chamada ao Banco de Dados | Content Cache (use cache) |
Chamada ao banco de dados não é um HTTP fetch |
| Cálculos complexos (50 ms+) | Content Cache | Qualquer função pode ser cacheada |
| Ler Arquivos/Configuração | Content Cache | Operações de arquivo não podem ser feitas via fetch |
| Processamento Misto HTTP + DB | Content Cache | Cache de Blocos Inteiros de Lógica |
▶ Exemplo: Combinando Dois Caches (Dificuldade: ⭐⭐⭐)
Saída:
A página renderiza conforme descrito acima, com a UI atualizando de acordo com o comportamento descrito.
// app/cache-combo/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
// 1. fetch cache: Chamar API Externa
async function getExternalPosts() {
return fetch('https://jsonplaceholder.typicode.com/posts', {
next: { tags: ['external-posts'], revalidate: 300 }
}).then(r => r.json())
}
// 2. use cache: Dados processados
async function getProcessedPosts() {
'use cache'
cacheTag('processed-posts')
cacheLife({ stale: 60, revalidate: 600 })
const raw = await getExternalPosts() // Dependência do fetch cache
return (raw as any[]).map((p: any) => ({
id: p.id,
title: p.title.toUpperCase(),
summary: p.body.slice(0, 100),
}))
}
export default async function CacheComboPage() {
const posts = await getProcessedPosts()
return (
<div>
<h1>Posts Processados ({posts.length})</h1>
<ul>{posts.map((p: any) => (
<li key={p.id}><strong>{p.title}</strong><p>{p.summary}</p></li>
))}</ul>
</div>
)
}
Saída:
Obtém dados no lado do servidor e renderiza uma lista de itens a partir dos dados.
6. Exemplo Completo: Arquitetura de Cache para um Sistema de Gerenciamento de Usuários
// app/cache-system/page.tsx — Uma Arquitetura de Cache Completa
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
// ======== Camada de Dados (Cache Explícito com use cache) ========
type User = { id: number; name: string; email: string }
type Post = { id: number; title: string; body: string }
async function getUsers(): Promise<User[]> {
'use cache'
cacheTag('users')
cacheLife({ stale: 120, revalidate: 600 })
return fetch('https://jsonplaceholder.typicode.com/users').then(r => r.json())
}
async function getUserPosts(userId: number): Promise<Post[]> {
'use cache'
cacheTag('posts', `user-posts-${userId}`)
cacheLife({ stale: 60, revalidate: 300 })
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`).then(r => r.json())
}
async function getStats(users: User[]) {
'use cache'
cacheTag('stats')
cacheLife({ stale: 300, revalidate: 1800 })
return {
totalUsers: users.length,
avgNameLength: users.reduce((s, u) => s + u.name.length, 0) / users.length,
}
}
// ======== Componentes da Página ========
export default async function CacheSystemPage() {
const users = await getUsers()
const stats = await getStats(users)
return (
<div style={{ maxWidth: 900, margin: '0 auto', padding: 24 }}>
<h1>Gerenciamento de Usuários</h1>
<StatsCard stats={stats} />
<div style={{ display: 'grid', gap: 16, marginTop: 24 }}>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
</div>
)
}
// ======== Componente filho (Cache Independente) ========
async function UserCard({ user }: { user: User }) {
const posts = await getUserPosts(user.id)
return (
<div style={{ border: '1px solid #ddd', borderRadius: 8, padding: 16 }}>
<h2>{user.name}</h2>
<p style={{ color: '#666' }}>{user.email}</p>
<details>
<summary>Posts ({posts.length})</summary>
<ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
</details>
</div>
)
}
function StatsCard({ stats }: { stats: { totalUsers: number; avgNameLength: number } }) {
return (
<div style={{ background: '#f0f4ff', borderRadius: 8, padding: 16, display: 'flex', gap: 32 }}>
<div><strong>Total de Usuários</strong><p style={{ fontSize: 24 }}>{stats.totalUsers}</p></div>
<div><strong>Tamanho Médio do Nome</strong><p style={{ fontSize: 24 }}>{stats.avgNameLength.toFixed(1)}</p></div>
</div>
)
}
// ======== Tarefas Administrativas ========
// app/cache-system/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function refreshUserData() {
revalidateTag('users') // Atualiza a lista de usuários
}
export async function refreshPosts(userId: number) {
revalidateTag(`user-posts-${userId}`) // Atualiza os posts de um usuário específico
}
export async function refreshAll() {
revalidateTag('users')
revalidateTag('posts')
revalidateTag('stats')
}
// app/cache-system/admin-button.tsx
'use client'
export function AdminControls() {
return (
<div style={{ display: 'flex', gap: 8, margin: '16px 0' }}>
<form action={async () => {
const { refreshUserData } = await import('./actions')
await refreshUserData()
}}>
<button type="submit">Atualizar Usuários</button>
</form>
<form action={async () => {
const { refreshAll } = await import('./actions')
await refreshAll()
}}>
<button type="submit">Atualizar Tudo</button>
</form>
</div>
)
}
▶ Exemplo: Verificando um Cache Hit (Dificuldade: ⭐⭐)
Saída:
Um formulário com campos de entrada e um botão de envio.
Ao enviar, a server action processa os dados e invalida as tags de cache relevantes.
Texto visível: Gerenciamento de Usuários
// app/cache-verify/page.tsx — Verificar se houve cache hit
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'
let callCount = 0
async function getUniqueId() {
'use cache'
cacheTag('unique-id')
cacheLife({ revalidate: 30 })
callCount++
return { id: crypto.randomUUID(), calls: callCount }
}
export default async function CacheVerifyPage() {
const [a, b, c] = await Promise.all([
getUniqueId(),
getUniqueId(),
getUniqueId(),
])
return (
<div>
<h1>Verificação de Cache</h1>
<p>Resultado A: {a.id} (chamada #{a.calls})</p>
<p>Resultado B: {b.id} (chamada #{b.calls})</p>
<p>Resultado C: {c.id} (chamada #{c.calls})</p>
<p><strong>Mesmo ID + calls = 1 → cache hit</strong></p>
</div>
)
}
Saída:
Conteúdo: Verificação de Cache | Mesmo ID + calls = 1 → cache hit
Saída:
Resultado A: 550e8400-e29b-41d4-a716-446655440000 (chamada #1)
Resultado B: 550e8400-e29b-41d4-a716-446655440000 (chamada #1) ← Cache Hit
Resultado C: 550e8400-e29b-41d4-a716-446655440000 (chamada #1) ← Cache Hit
❓ Perguntas Frequentes
P: Qual é a diferença entre
use cacheeuseMemo? R:useMemoé um hook do lado do cliente que armazena em cache cálculos apenas dentro do navegador e tem escopo limitado a uma única renderização.use cacheé uma diretiva do lado do servidor que persiste o cache entre requisições, suporta tags e tempos de expiração, e permite que Server Actions o invalidem sob demanda. Os dois têm casos de uso completamente diferentes.
P: Qual é a diferença entre "stale" e "revalidate" no
cacheLife? R: Dentro do períodostale, o resultado em cache é retornado diretamente (sem disparar atualização em segundo plano). Se o tempo excederstalemas for menor querevalidate, o resultado em cache ainda é retornado, mas um recálculo em segundo plano é disparado. Se o tempo excederrevalidate, o sistema aguarda um novo resultado. Configurações recomendadas: Definastalepara o atraso aceitável para o usuário, erevalidatepara a frescura máxima dos dados.
P: A mesma tag pode ser usada tanto para
fetchquanto parause cache? R: Sim.revalidateTag('products')limpará tanto a entrada rotulada comnext: { tags: ['products'] }do cache de dados do fetch quanto a entrada rotulada comcacheTag('products')do cache de conteúdo. Este é o mecanismo chave para alcançar a invalidação unificada de cache.
P: O
use cachepode ser usado em um client component? R: Não. A diretiva'use cache'é válida apenas em server components ou funções do servidor. Em client components, você deve usaruseMemoou soluções de cache do lado do cliente como React Query.
P: O que significa o prefixo
unstable_emunstable_cacheLifeeunstable_cacheTag? R: Significa que a API ainda está em desenvolvimento e pode mudar em versões futuras. Elas estão disponíveis no Next.js 16.2, mas recomendamos ficar de olho nas atualizações oficiais. O prefixounstable_é tipicamente removido após 1–2 versões principais (esperado na versão estável do Next.js 17 ou 18).
P: Onde os dados do Cache de Conteúdo são armazenados? R: Por padrão, o Cache de Conteúdo é armazenado em memória (persistido entre requisições em produção). Quando implantado na Vercel, usa armazenamento edge. Em ambientes self-hosted, é armazenado no sistema de arquivos ou em memória. O tamanho do cache é limitado pela memória do servidor; um cache grande consumirá recursos de memória.
📖 Resumo
- A diretiva
'use cache'torna os resultados de qualquer função do lado do servidor cacheáveis, não limitados a HTTP fetches cacheTag()Rotula conteúdo em cache; suporta tags dinâmicas (baseadas em parâmetros)cacheLife()Controla a política de expiração do cache (modo duplo stale + revalidate)revalidateTag()Limpa todas as tags correspondentes do fetch cache e do content cache de uma só vez- Cache implícito do
fetch(Data Cache) é adequado para requisições HTTP, enquanto o cache explícito comuse cache(Content Cache) é adequado para qualquer função - Granularidade de cache de grossa para fina: nível de página → nível de dados → nível de função; use
cachepara implementar cache no nível de função - Melhor prática: Use
use cachepara consultas ao banco de dados, cálculos complexos e funções de processamento de dados; use cache defetchpara APIs HTTP
📝 Exercícios
-
Exercício Básico (⭐): Crie um
app/cache-demo/page.tsxe use uma função'use cache'para envolver uma operação simulada demorada (await new Promise(resolve => setTimeout(resolve, 2000))). Chame-a três vezes na página e verifique se a segunda chamada começa com atraso zero (cache hit). -
Exercício Avançado (⭐⭐): Construa uma página que inclua uma lista de usuários e uma lista de artigos de usuários. Use
cacheLife({ revalidate: 300 })para a lista de usuários ecacheLife({ revalidate: 60 })para a lista de artigos. Adicione dois botões de Server Action na parte inferior da página para atualizar a aba de usuários e a aba de artigos, respectivamente. Verifique se as abas funcionam independentemente. -
Desafio (⭐⭐⭐): Implemente um "Painel de Análise de Dados" que obtenha dados de três fontes diferentes (API HTTP + banco de dados simulado + funções de cálculo) e use
use cachepara cache centralizado. Cada fonte de dados tem uma estratégiacacheLifediferente. Adicione um botão "Forçar Atualização de Tudo" que chamerevalidateTagpara atualizar todos os painéis simultaneamente. Construa uma ferramenta para rastrear taxas de cache hit.