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



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.

TSX
// 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.

100%
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

TSX
// 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:

TEXT 📖 Somente leitura
Renderiza a interface do componente getExpensiveData.
TSX
// 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:

TEXT 📖 Somente leitura
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:

TEXT 📖 Somente leitura
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

100%
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

TSX
// 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:

TEXT 📖 Somente leitura
Obtém dados e renderiza o resultado.
TSX
// 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:

TEXT 📖 Somente leitura
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:

TEXT 📖 Somente leitura
O componente renderiza a UI descrita no navegador.
TSX
// 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:

TEXT 📖 Somente leitura
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:

TEXT 📖 Somente leitura
A página renderiza conforme descrito acima, com a UI atualizando de acordo com o comportamento descrito.
TSX
// 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:

TEXT 📖 Somente leitura
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

TSX
// 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:

TEXT 📖 Somente leitura
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
TSX
// 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:

TEXT 📖 Somente leitura
Conteúdo: Verificação de Cache | Mesmo ID + calls = 1 → cache hit

Saída:

TEXT 📖 Somente leitura
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 cache e useMemo? 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íodo stale, o resultado em cache é retornado diretamente (sem disparar atualização em segundo plano). Se o tempo exceder stale mas for menor que revalidate, o resultado em cache ainda é retornado, mas um recálculo em segundo plano é disparado. Se o tempo exceder revalidate, o sistema aguarda um novo resultado. Configurações recomendadas: Defina stale para o atraso aceitável para o usuário, e revalidate para a frescura máxima dos dados.

P: A mesma tag pode ser usada tanto para fetch quanto para use cache? R: Sim. revalidateTag('products') limpará tanto a entrada rotulada com next: { tags: ['products'] } do cache de dados do fetch quanto a entrada rotulada com cacheTag('products') do cache de conteúdo. Este é o mecanismo chave para alcançar a invalidação unificada de cache.

P: O use cache pode 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 usar useMemo ou soluções de cache do lado do cliente como React Query.

P: O que significa o prefixo unstable_ em unstable_cacheLife e unstable_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 prefixo unstable_ é 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


📝 Exercícios

  1. Exercício Básico (⭐): Crie um app/cache-demo/page.tsx e 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).

  2. 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 e cacheLife({ 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.

  3. 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 cache para cache centralizado. Cada fonte de dados tem uma estratégia cacheLife diferente. Adicione um botão "Forçar Atualização de Tudo" que chame revalidateTag para atualizar todos os painéis simultaneamente. Construa uma ferramenta para rastrear taxas de cache hit.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%