Alternar tema

Cache no Next.js: guia completo para usar revalidate no momento certo

Easton editorial illustration: solo-founder business system console

Ali estava o “dado antigo” na tela, depois da vigésima atualização. Dez minutos antes, eu havia alterado manualmente o título no banco de dados, mas a página simplesmente não atualizava. Abri o código: revalidate: 60 estava declarado com todas as letras. Irritado, apaguei e troquei por revalidate: 10. Reiniciei o servidor. Atualizei a página. O dado antigo continuava lá.

O sistema de cache talvez seja a parte mais enlouquecedora do Next.js. São quatro camadas, três métodos de revalidação e ainda uma mudança incompatível da versão 14 para a 15. Você acha que definir revalidate: 60 resolve tudo? Talvez o Router Cache esteja atrapalhando, talvez o Full Route Cache não tenha sido invalidado ou talvez você esteja testando no ambiente de desenvolvimento.

Este artigo explica da forma mais direta possível:

  • Quantas camadas de cache o Next.js esconde e o que cada uma controla
  • Qual é a diferença entre revalidatePath, revalidateTag e updateTag, e quando usar cada um
  • Como investigar cada camada quando os dados não são atualizados

Se você também já encontrou dados desatualizados, um revalidate que parece não funcionar ou dúvidas sobre qual API usar, os próximos 12 minutos podem poupar várias noites em claro.

Por que o cache do Next.js é tão complexo

Por que existem tantas camadas?

Sinceramente, quando vi pela primeira vez que o Next.js tinha quatro camadas de cache, provavelmente fiz a mesma cara que você está fazendo agora. Request Memoization? Full Route Cache? O que é tudo isso? Não poderia ser mais simples?

Pensando com calma, porém, cada camada resolve um problema de desempenho em um contexto diferente:

  • Se dez componentes da sua árvore precisam dos dados do usuário, não faz sentido enviar dez solicitações iguais.
  • Se a lista de posts do seu blog muda poucas vezes por dia, não faz sentido renderizá-la de novo a cada acesso.
  • Se o usuário aperta o botão Voltar, não faz sentido obrigá-lo a esperar a página carregar novamente.

Cada cache tem sua responsabilidade. O problema é que eles afetam uns aos outros. Por isso, depois de alterar os dados, nem sempre fica claro qual cache deve ser invalidado.

Next.js 14 versus 15: uma revolução no cache

No fim de 2024, o Next.js 15 trouxe uma mudança importante: solicitações feitas com fetch deixaram de ser armazenadas em cache por padrão.

Antes (14):

fetch(url) // Cache por padrão, equivalente a cache: 'force-cache'

Agora (15):

fetch(url) // Sem cache por padrão, equivalente a cache: 'no-store'

Depois da atualização, muita gente viu o desempenho despencar porque dados antes armazenados automaticamente passaram a ser buscados em todas as solicitações. Houve muitas reclamações no fórum da Vercel, mas a justificativa oficial foi: “O explícito é melhor que o implícito; o cache deve ser uma escolha consciente do desenvolvedor, não um comportamento padrão.”

Faz sentido. Para projetos existentes, porém, continua sendo uma mudança incompatível.

Visão geral das quatro camadas

De forma simples, os dados atravessam estas quatro camadas entre o servidor e o navegador:

  1. Request Memoization (memorização de solicitações)
    Escopo: ciclo de renderização de uma única solicitação
    Responsável: React

  2. Data Cache (cache de dados)
    Escopo: servidor, persistente entre solicitações
    Responsável: Next.js

  3. Full Route Cache (cache completo da rota)
    Escopo: servidor, rotas estáticas
    Responsável: Next.js

  4. Router Cache (cache do roteador)
    Escopo: memória do navegador no cliente
    Responsável: Next.js

O fluxo se parece com isto:

Acesso do usuário → Router Cache (cliente) → Full Route Cache (servidor)
                                           ↓
                              Data Cache → Request Memoization → fonte de dados

Seu revalidate afeta principalmente o Data Cache e o Full Route Cache. Para limpar o Router Cache, é preciso chamar router.refresh() ou fazer uma atualização forçada.

É por isso que, às vezes, mesmo depois da revalidação, o cliente ainda exibe dados antigos: o Router Cache continua ativo.

Na próxima seção, vamos examinar as quatro camadas, entender o que cada uma faz e quando é criada ou invalidada.

As quatro camadas de cache em detalhes

Request Memoization (memorização de solicitações)

O que é isso?

Na verdade, esse é um recurso do React 18, não algo criado especificamente pelo Next.js. Em resumo, se vários componentes fazem a mesma solicitação GET durante um ciclo de renderização, o React as combina automaticamente em uma só.

Veja um exemplo:

// app/page.tsx
async function UserProfile() {
  const user = await fetch('https://api.example.com/user/123')
  return <div>{user.name}</div>
}

async function UserAvatar() {
  const user = await fetch('https://api.example.com/user/123')  // Mesma solicitação
  return <img src={user.avatar} />
}

export default function Page() {
  return (
    <>
      <UserProfile />
      <UserAvatar />
    </>
  )
}

Os dois componentes solicitam a mesma URL, mas apenas uma solicitação é enviada. O React memoriza o primeiro resultado e o reutiliza na segunda chamada.

Ciclo de vida muito curto

Esse cache vale apenas durante uma renderização. Quando ela termina, o cache é descartado. Na próxima atualização da página, começa uma nova solicitação.

Pontos de atenção

  • Funciona apenas em Server Components.
  • Funciona apenas para solicitações GET; POST e PUT não são memorizadas.
  • O efeito pode não ficar evidente no ambiente de desenvolvimento, pois cada alteração no código dispara uma nova renderização.

Quando isso é útil?

Talvez você nem precise se preocupar com essa camada. É uma otimização automática do React e não pode ser controlada manualmente. Ela aparece aqui para deixar claro que repetir o mesmo fetch em vários componentes não significa necessariamente enviar várias solicitações.

Data Cache (cache de dados)

Este é o ponto central

O Data Cache é o núcleo do cache do Next.js. Ele armazena os resultados das solicitações fetch no sistema de arquivos do servidor, de forma persistente entre solicitações, usuários e implantações.

A enorme diferença entre Next.js 14 e 15

Next.js 14:

fetch('https://api.example.com/posts')
// Equivalente a
fetch('https://api.example.com/posts', { cache: 'force-cache' })
// Resultado: os dados ficam armazenados até uma revalidação manual

Next.js 15:

fetch('https://api.example.com/posts')
// Equivalente a
fetch('https://api.example.com/posts', { cache: 'no-store' })
// Resultado: uma nova solicitação é feita sempre; não há cache

Para armazenar dados em cache no Next.js 15

Opção 1: configurar uma solicitação específica

fetch('https://api.example.com/posts', {
  cache: 'force-cache',
  next: { revalidate: 3600 }  // Revalida após uma hora
})

Opção 2: configurar a rota inteira

// app/blog/page.tsx
export const revalidate = 3600

export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts')
  // ...
}

Quando ele é invalidado?

O Data Cache pode ser invalidado de três formas:

  1. O intervalo definido em revalidate termina, por exemplo, após 3.600 segundos.
  2. revalidatePath() ou revalidateTag() é chamado manualmente.
  3. O aplicativo é implantado novamente.

E se vários fetch tiverem intervalos de revalidação diferentes?

Se uma página tem vários fetch com intervalos distintos, o Next.js usa o menor deles como intervalo de revalidação da página inteira.

async function Page() {
  const posts = await fetch('...', { next: { revalidate: 60 } })    // 60 segundos
  const user = await fetch('...', { next: { revalidate: 3600 } })  // Uma hora

  // Na prática, a página inteira será revalidada a cada 60 segundos
}

Full Route Cache (cache completo da rota)

Cache no nível do HTML

Se o Data Cache armazena os dados, o Full Route Cache armazena o HTML completo da página e o RSC Payload, isto é, os dados serializados dos React Server Components.

Quando uma rota é armazenada?

Somente rotas com renderização estática entram nesse cache. Uma rota é estática quando seu conteúdo pode ser determinado durante o build.

Por outro lado, uma página passa a usar renderização dinâmica e não é armazenada se utilizar:

  • cookies()
  • headers()
  • searchParams
  • Funções instáveis, como Math.random() ou Date.now()

Como saber se a página é estática ou dinâmica?

Execute npm run build. O terminal exibirá algo semelhante a:

Route (app)                              Size     First Load JS
┌ ○ /                                    5 kB           87 kB
├ ● /blog                                1 kB           88 kB
└ ƒ /api/user                            0 kB           87 kB

○  (Static)  Renderizado automaticamente como HTML estático, sem dados dinâmicos
●  (SSG)     Gerado automaticamente como HTML estático + JSON, com getStaticProps
ƒ  (Dynamic) Renderizado sob demanda no servidor

Os símbolos ○ e ● indicam uma rota estática, que será armazenada em cache. O símbolo ƒ indica uma rota dinâmica, sem cache.

Como forçar o modo estático ou dinâmico

// Força renderização estática
export const dynamic = 'force-static'

// Força renderização dinâmica
export const dynamic = 'force-dynamic'

Quando ele é invalidado?

O Full Route Cache pode ser invalidado quando:

  1. O Data Cache é invalidado. Como os dados mudaram, a página precisa ser renderizada novamente.
  2. revalidatePath('/blog') é chamado.
  3. O aplicativo é implantado novamente.

Router Cache (cache do roteador)

Um truque no cliente

O Router Cache fica na memória do navegador. Depois que o usuário visita uma página, o Next.js armazena seu conteúdo no cliente. Ao voltar ou navegar novamente até ela, usa esse conteúdo sem consultar o servidor.

Há mais um truque: a pré-busca

Se você usa <Link href="/about">, quando o link entra na área visível da tela, o Next.js faz a pré-busca automática do conteúdo de /about e o armazena no Router Cache. Quando o usuário clica, a navegação é instantânea.

Ciclo de vida no Next.js 14

  • Rotas estáticas: 5 minutos
  • Rotas dinâmicas: 30 segundos

Mudanças no Next.js 15

Por padrão, o Next.js 15 não ativa o Router Cache, ou usa um intervalo muito curto. Para ativá-lo, configure next.config.js:

// next.config.js
module.exports = {
  experimental: {
    staleTimes: {
      dynamic: 30,      // Cache de rota dinâmica por 30 segundos
      static: 180,      // Cache de rota estática por 180 segundos
    },
  },
}

Quando ele é invalidado?

  • Quando o intervalo de cache termina.
  • Quando o usuário faz uma atualização forçada com Ctrl+Shift+R.
  • Quando router.refresh() é chamado.

Por que revalidate parece não funcionar?

Muitas vezes, revalidatePath foi chamado no servidor e os dados foram atualizados, mas o usuário ainda vê a versão antiga ao atualizar a página. Quase sempre, o motivo é que o Router Cache ainda está válido.

Soluções:

  1. O usuário faz uma atualização forçada, embora você não possa exigir isso dele.
  2. Depois de atualizar os dados, chame router.refresh() em um Client Component.
  3. Reduza o intervalo do Router Cache.

Todos os métodos de revalidação

Agora que as quatro camadas estão claras, chegamos à parte mais prática: como invalidar o cache.

O Next.js oferece vários métodos de revalidação, cada um adequado a uma situação. Entender as diferenças economiza muito tempo de depuração.

Revalidação baseada em tempo

A opção mais comum

A revalidação baseada em tempo busca os dados novamente, de forma automática, a cada X segundos. Ela é o mecanismo central do ISR, ou Incremental Static Regeneration.

Duas formas de configurar

Opção 1: no fetch

const res = await fetch('https://api.example.com/posts', {
  next: { revalidate: 3600 }  // 3.600 segundos = 1 hora
})

Opção 2: no nível superior do arquivo da rota

// app/blog/page.tsx
export const revalidate = 3600

export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts')
  return <PostList posts={posts} />
}

Como o ISR funciona

Imagine que você definiu revalidate: 3600:

  1. O primeiro usuário acessa a página: um HTML estático é gerado e armazenado por uma hora.
  2. Durante essa hora, todos recebem o mesmo HTML em cache, com muita rapidez.
  3. Depois de uma hora, o próximo usuário acessa: ele ainda recebe o HTML antigo, sem esperar.
  4. Ao mesmo tempo, o Next.js gera um novo HTML em segundo plano.
  5. Quando esse HTML fica pronto, os acessos seguintes recebem o conteúdo novo.

Esse mecanismo é chamado de stale-while-revalidate, ou seja, conteúdo antigo é entregue durante a revalidação. A vantagem é que o usuário não espera; a desvantagem é que um usuário ainda verá dados desatualizados.

Quando usar

  • Lista de posts atualizada algumas vezes por hora.
  • Página inicial de notícias atualizada a cada 30 minutos.
  • Catálogo de produtos atualizado uma vez por dia.

Problema comum 1: não funciona em desenvolvimento

Muita gente configura revalidate e conclui que ele não funciona. A primeira pergunta deve ser: o teste está sendo feito em desenvolvimento ou produção?

Em desenvolvimento, com npm run dev, o Next.js desativa grande parte dos caches e renderiza de novo a cada solicitação. Teste no ambiente de produção:

npm run build
npm start

Problema comum 2: vários fetch com intervalos diferentes

Como vimos, se uma página tem vários fetch com intervalos distintos, o Next.js usa o menor. Há, porém, um detalhe: o Data Cache ainda respeita o intervalo individual de cada fetch.

async function Page() {
  // Esta solicitação é revalidada a cada 60 segundos
  const posts = await fetch('...', { next: { revalidate: 60 } })

  // Esta solicitação é revalidada a cada 3.600 segundos
  const user = await fetch('...', { next: { revalidate: 3600 } })
}

A página será renderizada novamente a cada 60 segundos, mas os dados de user continuarão armazenados durante uma hora. Nesse período, a página muda a cada minuto, porém os dados do usuário permanecem iguais; somente após uma hora eles serão atualizados.

Pode parecer confuso, mas é um comportamento coerente.

Revalidação sob demanda com revalidatePath

Atualizações disparadas pelo usuário

A revalidação baseada em tempo funciona como um temporizador. Já revalidatePath funciona como um botão: quando um evento específico acontece, como a publicação de um post, você invalida o cache manualmente.

Como usar

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'

export async function publishPost(formData) {
  // Lógica de publicação do post
  await db.posts.create({ ... })

  // Invalida o cache da lista do blog
  revalidatePath('/blog')
}

Depois, chame a função em um Client Component:

// app/components/PublishButton.tsx
'use client'

import { publishPost } from '@/app/actions'

export function PublishButton() {
  return (
    <form action={publishPost}>
      <button type="submit">Publicar post</button>
    </form>
  )
}

Tipos de caminho

revalidatePath aceita um tipo de caminho:

// Revalida apenas a página /blog
revalidatePath('/blog', 'page')

// Revalida todas as páginas sob /blog, incluindo /blog/post-1 e /blog/post-2
revalidatePath('/blog', 'layout')

Importante: a página não é gerada imediatamente

Muita gente acredita que a página é gerada assim que revalidatePath é chamado. Não é assim.

revalidatePath apenas marca o cache como inválido. A nova geração só acontece quando o próximo usuário acessa a página.

Na prática:

  1. Você chama revalidatePath('/blog').
  2. O cache é limpo.
  3. O próximo usuário acessa /blog: só então o HTML é gerado novamente, e ele precisa esperar.
  4. Os acessos seguintes recebem o conteúdo novo.

Quando usar

  • Atualizar uma lista depois que o usuário publica conteúdo.
  • Atualizar páginas relacionadas depois que um administrador muda uma configuração.
  • Atualizar a página atual depois do envio de um formulário.

Revalidação sob demanda com revalidateTag

Controle de invalidação mais flexível

revalidatePath invalida por caminho; revalidateTag invalida por tag. Você pode associar tags aos dados e depois invalidar de uma vez todo o cache que usa determinada tag.

Como usar

Etapa 1: associe tags aos fetch

const posts = await fetch('https://api.example.com/posts', {
  next: {
    revalidate: 3600,
    tags: ['posts']  // Adiciona a tag 'posts'
  }
})

const authors = await fetch('https://api.example.com/authors', {
  next: {
    revalidate: 3600,
    tags: ['posts', 'authors']  // Uma solicitação pode ter várias tags
  }
})

Etapa 2: invalide o cache de uma tag

'use server'

import { revalidateTag } from 'next/cache'

export async function publishPost() {
  await db.posts.create({ ... })

  // Todo dado marcado com 'posts' será invalidado
  revalidateTag('posts')
}

Estratégia stale-while-revalidate com profile=“max”

No Next.js 15, recomenda-se usar profile="max":

revalidateTag('posts', { profile: 'max' })

Com profile="max":

  1. O dado é marcado como expirado, mas não é removido imediatamente.
  2. No próximo acesso, o dado antigo é devolvido rapidamente.
  3. Ao mesmo tempo, os dados novos são buscados em segundo plano.
  4. Quando ficam prontos, passam a ser usados nas solicitações seguintes.

É melhor que o comportamento padrão porque o usuário não precisa esperar.

Diferenças entre revalidatePath e revalidateTag

CritériorevalidatePathrevalidateTag
GranularidadePor caminhoPor tag de dados
Várias páginasLimitado aos caminhos informadosPode atravessar várias páginas
PrecisãoMenorMaior
ComplexidadeSimplesExige planejar as tags

Quando usar tags?

Quando os mesmos dados aparecem em várias páginas, tags são mais adequadas.

Imagine um blog com:

  • Lista de posts em /blog.
  • Página de post em /blog/[slug].
  • Página de autor em /author/[id].
  • Seção de posts recentes na página inicial.

As quatro páginas usam os mesmos dados de posts. Com revalidatePath, seria necessário fazer isto:

revalidatePath('/blog')
revalidatePath('/blog/[slug]')
revalidatePath('/author/[id]')
revalidatePath('/')

Com a tag posts, basta:

revalidateTag('posts')

Todas as páginas que usam essa tag serão invalidadas de uma vez.

Novo: updateTag no Next.js 15

Invalidação imediata em vez de adiada

updateTag é uma API nova do Next.js 15. Ao contrário de revalidateTag, ela remove o cache imediatamente, em vez de apenas marcá-lo como expirado.

'use server'

import { updateTag } from 'next/cache'

export async function updateUserProfile(userId, newData) {
  await db.users.update({ where: { id: userId }, data: newData })

  // Invalida imediatamente o cache dos dados do usuário
  updateTag(`user-${userId}`)
}

Diferenças em relação a revalidateTag

CritériorevalidateTagupdateTag
Forma de invalidaçãoMarca como expirado e atualiza em segundo plano no próximo acessoRemove o cache imediatamente
Próximo acessoEntrega dados antigos e busca os novos em segundo planoAguarda a busca dos dados novos
LimitaçãoPode ser usado em vários contextosSó pode ser usado em Server Actions
CenárioCasos gerais, priorizando velocidadeCenários de leitura após a própria gravação

O que significa ler a própria gravação?

Imagine que o usuário alterou o apelido em sua página de perfil. Depois de clicar em Salvar, a página deve mostrar imediatamente o novo apelido, não o valor antigo enquanto espera uma atualização em segundo plano.

Nesse caso, use updateTag:

export async function updateProfile(formData) {
  const userId = getCurrentUserId()

  await db.users.update({
    where: { id: userId },
    data: { nickname: formData.get('nickname') }
  })

  // Invalida imediatamente para que a próxima leitura veja os dados mais recentes
  updateTag(`user-${userId}`)

  revalidatePath('/profile')
}

Novo: diretiva use cache no Next.js 15

Declaração explícita de cache

O Next.js 15 introduziu a diretiva 'use cache', que permite marcar explicitamente as funções cujos resultados devem ser armazenados.

Como usar

'use cache'

export async function getPopularPosts() {
  const posts = await db.posts.findMany({
    orderBy: { views: 'desc' },
    take: 10
  })
  return posts
}

Com cacheTag

import { unstable_cacheTag as cacheTag } from 'next/cache'

'use cache'

export async function getPostsByAuthor(authorId) {
  cacheTag('posts', `author-${authorId}`)

  return await db.posts.findMany({
    where: { authorId }
  })
}

Depois, o cache pode ser invalidado com revalidateTag:

revalidateTag(`author-${authorId}`)

Por que isso é necessário?

No Next.js 15, como fetch não usa cache por padrão, é preciso declarar essa intenção se você não quiser consultar o banco de dados a cada solicitação. use cache torna essa intenção explícita.

Guia para diagnosticar problemas comuns

Com a teoria concluída, chegamos à parte mais útil: como investigar problemas de cache.

A seguir estão quatro problemas frequentes, cada um com etapas de diagnóstico e soluções.

Problema 1: revalidate foi configurado, mas não funciona

Sintoma

Você definiu export const revalidate = 60, mas dez minutos depois a página ainda mostra dados antigos.

Etapas de diagnóstico

1. Confirme que o teste está sendo feito em produção

Esse é o erro mais comum. O ambiente de desenvolvimento, iniciado com npm run dev, desativa grande parte dos caches.

Teste desta forma:

npm run build
npm start

2. Confira se a rota usa renderização dinâmica

Execute npm run build e observe a saída:

Route (app)                Size
├ ○ /blog                  1 kB    ← Estática, usa cache
└ ƒ /profile               2 kB    ← Dinâmica, não usa cache

Se a rota estiver marcada com ƒ, revalidate não terá efeito porque rotas dinâmicas não são armazenadas em cache.

Código que pode tornar uma rota dinâmica:

// Estes recursos tornam a rota dinâmica
import { cookies } from 'next/headers'
import { headers } from 'next/headers'

export default function Page({ searchParams }) {  // Usa searchParams
  const cookieStore = cookies()  // Usa cookies
  // ...
}

Soluções:

  • Se a renderização dinâmica não for necessária, remova esses recursos.
  • Se ela for necessária, não espere que revalidate funcione; use revalidação sob demanda.

3. Confira a versão do Next.js

O comportamento padrão é diferente nas versões 14 e 15. Se você acabou de atualizar para a 15, muitos dados que antes eram armazenados deixaram de ser.

Solução no Next.js 15:

// Ativa o cache explicitamente
fetch(url, {
  cache: 'force-cache',
  next: { revalidate: 60 }
})

// Ou usa a diretiva use cache
'use cache'
export async function getData() {
  // ...
}

4. Confira se o Router Cache está interferindo

Mesmo que os dados do servidor tenham sido atualizados, o Router Cache pode continuar com a versão antiga no cliente.

Soluções:

  • Faça uma atualização forçada com Ctrl+Shift+R.
  • No Next.js 15, configure staleTimes com intervalos menores.

Problema 2: os dados mudaram, mas a página ainda mostra a versão antiga

Sintoma

Você alterou os dados diretamente no banco ou chamou revalidatePath, mas a página continua exibindo a versão antiga.

Estratégia: verifique as quatro camadas, uma por uma

Camada 1: Router Cache no cliente

O cache do cliente é o mais fácil de esquecer.

Teste rápido:

  • Faça uma atualização forçada com Ctrl+Shift+R.
  • Se os dados forem atualizados, o problema está no Router Cache.

Solução:

'use client'

import { useRouter } from 'next/navigation'

export function RefreshButton() {
  const router = useRouter()

  return (
    <button onClick={() => router.refresh()}>
      Atualizar
    </button>
  )
}

Ou configure intervalos menores em next.config.js:

module.exports = {
  experimental: {
    staleTimes: {
      dynamic: 0,    // Desativa o cache de rotas dinâmicas
      static: 30,    // Cache de rotas estáticas por 30 segundos
    },
  },
}

Camada 2: Full Route Cache no servidor

Confira se a rota é estática. Nesse caso, o HTML inteiro está armazenado.

Teste rápido:

# Abra a página em uma nova janela anônima
# Se os dados ainda forem antigos, o cache está no servidor

Solução:

'use server'

import { revalidatePath } from 'next/cache'

export async function updateData() {
  await db.update({ ... })

  // Invalida o cache da rota
  revalidatePath('/your-page')
}

Camada 3: Data Cache no servidor

Confira a configuração do fetch.

Teste rápido:

Adicione ao fetch um log com a data e hora:

const data = await fetch(url)
console.log('Fetched at:', new Date().toISOString())

Atualize a página. Se o horário não mudar, os dados vieram do cache.

Soluções:

Opção 1: associe uma tag aos dados e depois a invalide

// Ao buscar os dados
const data = await fetch(url, {
  next: { tags: ['my-data'] }
})

// Ao atualizar os dados
revalidateTag('my-data')

Opção 2: desative o cache temporariamente para testar

const data = await fetch(url, {
  cache: 'no-store'  // Sem cache
})

Camada 4: Request Memoization no servidor

Essa camada raramente causa problemas porque dura apenas uma solicitação. Se as três anteriores estão corretas, provavelmente a causa não é o cache, mas a própria fonte de dados.

Problema 3: devo usar revalidatePath ou revalidateTag?

Árvore de decisão

Você precisa invalidar o cache
    |
    ├─ A mudança afeta apenas uma página
    |      → Use revalidatePath('/specific-page')
    |
    ├─ A mudança afeta todas as páginas sob um caminho
    |      → Use revalidatePath('/blog', 'layout')
    |
    ├─ Os dados aparecem em páginas com caminhos diferentes
    |      → Use revalidateTag('your-tag')
    |
    └─ A invalidação precisa ser imediata após uma edição do usuário
           → Use updateTag('your-tag') no Next.js 15

Exemplo real: um blog

Imagine que o sistema tem estas páginas:

  • Página inicial, com os três posts mais recentes.
  • Lista em /blog, com todos os posts.
  • Detalhes em /blog/[slug], com um único post.
  • Página /author/[id], com todos os posts de um autor.

Estratégia com tags:

// Associa tags ao buscar os dados
async function getPosts() {
  return fetch('https://api.example.com/posts', {
    next: {
      revalidate: 3600,
      tags: ['posts']  // Todos os dados de posts recebem esta tag
    }
  })
}

async function getPostBySlug(slug) {
  return fetch(`https://api.example.com/posts/${slug}`, {
    next: {
      revalidate: 3600,
      tags: ['posts', `post-${slug}`]  // Tag geral e tag específica
    }
  })
}

async function getPostsByAuthor(authorId) {
  return fetch(`https://api.example.com/posts?author=${authorId}`, {
    next: {
      revalidate: 3600,
      tags: ['posts', `author-${authorId}-posts`]
    }
  })
}

Ao publicar um post:

export async function publishPost(formData) {
  await db.posts.create({ ... })

  // Basta invalidar a tag 'posts'
  // Todas as páginas que usam essa tag serão atualizadas
  revalidateTag('posts')
}

Ao alterar um post específico:

export async function updatePost(slug, newData) {
  await db.posts.update({ where: { slug }, data: newData })

  // Invalida apenas o cache relacionado a este post
  revalidateTag(`post-${slug}`)

  // Ou, se também quiser atualizar as listagens
  revalidateTag('posts')
}

Problema 4: o cache deixou de funcionar após migrar do Next.js 14 para o 15

Sintoma

Depois da atualização, dados que antes ficavam armazenados passaram a ser solicitados novamente a cada acesso, e o desempenho caiu muito.

Causa

O Next.js 15 mudou três comportamentos padrão:

  1. fetch passou de force-cache para no-store.
  2. Route Handlers GET deixaram de usar cache por padrão.
  3. O Router Cache deixou de ser ativado por padrão.

Plano de migração

Opção 1: ative o cache explicitamente, recomendada

// Antes, no Next.js 14
const data = await fetch(url)

// Agora, no Next.js 15: declaração explícita
const data = await fetch(url, {
  cache: 'force-cache',
  next: { revalidate: 3600 }
})

Opção 2: use a diretiva use cache

'use cache'

export async function getPostList() {
  const posts = await db.posts.findMany()
  return posts
}

Opção 3: ative o Router Cache

// next.config.js
module.exports = {
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
}

Comparação antes e depois da migração:

// Next.js 14: cache implícito
export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts')
  // Armazenado automaticamente
}

// Next.js 15: cache explícito
'use cache'  // Adicione esta linha

export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts', {
    cache: 'force-cache',  // Ou adicione esta opção
    next: { revalidate: 3600 }
  })
}

Minha recomendação:

Não espere uma migração automática perfeita. Revise cada acesso a dados e decida conscientemente o que precisa ou não de cache. Dá trabalho, mas é mais saudável no longo prazo: você saberá exatamente quais dados estão armazenados e quais não estão.

Boas práticas e estratégia de escolha

Depois de toda essa explicação, podemos resumir quando usar cada estratégia de cache.

Fluxograma para escolher a estratégia de cache

Com que frequência seus dados mudam?
    |
    ├─ Quase nunca, como páginas Sobre e documentação de ajuda
    |      → Geração estática, sem revalidate
    |      → Faça uma nova implantação quando houver mudanças
    |
    ├─ Periodicamente, por exemplo a cada hora ou dia
    |      → ISR + revalidação baseada em tempo
    |      → export const revalidate = 3600
    |
    ├─ Sem periodicidade, como conteúdo publicado por usuários
    |      → Revalidação sob demanda
    |      → revalidatePath ou revalidateTag
    |
    └─ Em tempo real, como chats e painéis ao vivo
           → Renderização dinâmica + cache: 'no-store'
           → Não use cache

Estratégia para nomear tags

Se você optar por revalidateTag, vale seguir uma convenção como esta:

Níveis de granularidade

  • Baixa granularidade, para invalidação em lote:

    • posts: todos os posts.
    • products: todos os produtos.
    • users: todos os usuários.
  • Granularidade média, por categoria ou estado:

    • posts:published: posts publicados.
    • posts:draft: rascunhos.
    • products:category:electronics: produtos eletrônicos.
  • Alta granularidade, para um recurso específico:

    • post:id:123: post de ID 123.
    • user:profile:456: perfil do usuário de ID 456.

Convenção recomendada

Use namespaces no formato entity:type:id:

// Ao buscar os dados
const post = await fetch(`/api/posts/${id}`, {
  next: {
    tags: [
      'posts',                    // Baixa granularidade
      'posts:published',          // Granularidade média
      `post:id:${id}`             // Alta granularidade
    ]
  }
})

// Escolha a granularidade na hora de invalidar
revalidateTag('posts')              // Invalida todos os posts
revalidateTag('posts:published')    // Invalida apenas os publicados
revalidateTag(`post:id:${id}`)      // Invalida um post específico

Recomendações de desempenho

1. Não exagere no cache

Mais cache nem sempre é melhor. O excesso pode causar:

  • Dados inconsistentes.
  • Depuração mais difícil.
  • Desperdício de armazenamento.

Regra prática:

  • Dados personalizados do usuário, como carrinho e preferências → não armazene.
  • Dados públicos, como listas de produtos e posts → armazene.
  • Dados em tempo real, como estoque e usuários online → não armazene ou use um intervalo muito curto.

2. Escolha um intervalo razoável de revalidate

Não use intervalos curtos demais. Definir revalidate: 1 significa que a página pode ser gerada outra vez a cada segundo, anulando os benefícios do cache.

Valores recomendados:

  • Notícias: 30 a 60 minutos.
  • Posts de blog: 1 a 2 horas.
  • Catálogo de produtos: 2 a 4 horas.
  • Páginas estáticas: 24 horas ou mais.

3. Use stale-while-revalidate

No Next.js 15, profile="max" aplica essa estratégia:

revalidateTag('posts', { profile: 'max' })

O usuário sempre recebe conteúdo armazenado rapidamente, enquanto o sistema atualiza o cache em segundo plano. Isso proporciona uma experiência melhor.

4. Monitore a taxa de acerto do cache

Adicione isto a .env.local:

NEXT_PRIVATE_DEBUG_CACHE=1

Depois, inicie o servidor de produção. O console exibirá os acertos do cache:

○ GET /blog 200 in 45ms (cache: HIT)
○ GET /about 200 in 12ms (cache: SKIP)

Confira periodicamente se sua estratégia está funcionando.

Diferenças entre desenvolvimento e produção

Aviso importante:

O cache se comporta de forma completamente diferente em desenvolvimento, com npm run dev, e em produção, com npm start.

RecursoDesenvolvimentoProdução
Data CacheQuase todo desativadoTotalmente ativado
Full Route CacheDesativadoAtivado para rotas estáticas
Request MemoizationAtivadoAtivado
Router CacheAtivado, mas por pouco tempoIntervalo completo

Forma correta de testar o cache:

# 1. Gere o build de produção
npm run build

# 2. Confira o tipo de cada rota na saída
#    ○ = estática, usa cache
#    ƒ = dinâmica, não usa cache

# 3. Inicie o servidor de produção
npm start

# 4. Teste o comportamento do cache
# Abra a página no navegador e depois altere a fonte de dados
# Atualize a página e confira se os dados antigos ainda aparecem

# 5. Teste revalidate
# Espere o intervalo terminar, acesse de novo e confira a atualização

Não depure problemas de cache no ambiente de desenvolvimento. Esse é o erro mais comum: muita gente perde horas em desenvolvimento e conclui que revalidate não funciona.

Conclusão

Depois de tudo isso, os pontos essenciais são estes:

1. Entenda a função das quatro camadas

Não as confunda. O Router Cache fica no cliente; os outros três ficam no servidor. revalidate afeta principalmente o Data Cache e o Full Route Cache.

2. Escolha o método de revalidação adequado

  • Atualização periódica → export const revalidate = 3600.
  • Ação do usuário que afeta uma página → revalidatePath('/page').
  • Ação do usuário que afeta várias páginas → revalidateTag('tag').
  • Invalidação imediata → updateTag('tag') no Next.js 15.

3. Teste em produção

Lembre-se sempre: o comportamento do cache em desenvolvimento não é representativo. Para testá-lo, use npm run build && npm start.

4. Investigue uma camada por vez

Quando os dados não forem atualizados, confira nesta ordem:

  1. Router Cache no cliente → faça uma atualização forçada.
  2. Full Route Cache no servidor → use revalidatePath.
  3. Data Cache no servidor → use revalidateTag.
  4. A própria fonte de dados.

5. O explícito é melhor que o implícito, a filosofia do Next.js 15

O Next.js 15 mudou o cache de ativado por padrão para desativado por padrão. Isso obriga você a decidir conscientemente quais dados devem ser armazenados. Dá mais trabalho, mas torna o código mais claro e fácil de manter no longo prazo.


Se você chegou até aqui, já tem uma visão completa do cache no Next.js. Na próxima vez que os dados não forem atualizados, saberá por onde começar.

O cache é complexo, mas, depois de entendido, torna-se um dos recursos mais poderosos do Next.js. Usado com cuidado, ele deixa seu aplicativo muito rápido; usado sem critério, vira uma armadilha criada por você mesmo.

Que seu aplicativo tenha desempenho excelente e zero bugs!

Fluxo completo para usar o cache do Next.js

Etapas completas para entender as quatro camadas de cache, escolher o método de revalidação e investigar dados que não são atualizados.

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Entenda as quatro camadas de cache do Next.js

    As quatro camadas são:

    1. Request Memoization (eliminação de solicitações duplicadas)
    • Dentro da mesma renderização, uma URL idêntica gera apenas uma solicitação
    • É automática e não exige configuração
    • Ciclo de vida: uma única solicitação

    2. Data Cache (cache do fetch)
    • Armazena em cache as solicitações feitas com fetch
    • Comportamento padrão: o Next.js 14 armazena; o 15 não
    • Configuração: opção cache

    3. Full Route Cache (cache completo da rota)
    • Armazena o HTML de toda a rota
    • Páginas estáticas são armazenadas automaticamente
    • Configuração: revalidate

    4. Router Cache (cache de rotas no cliente)
    • Cache usado durante a navegação no cliente
    • É automático e não exige configuração
    • Ciclo de vida: durante a sessão

    Ponto-chave: o Router Cache fica no cliente; os outros três ficam no servidor.
  2. 2

    Step 2: Escolha o método de revalidação adequado

    Há três opções:

    1. Atualização periódica (export const revalidate)
    ```tsx
    export const revalidate = 3600 // Revalida após 3.600 segundos
    ```
    • Indicado para: conteúdo atualizado periodicamente
    • Configuração: em page.tsx ou layout.tsx

    2. Ação do usuário que afeta uma página (revalidatePath)
    ```tsx
    import { revalidatePath } from 'next/cache'

    revalidatePath('/blog/post-1')
    ```
    • Indicado para: atualizar uma única página após uma ação do usuário
    • Uso: em uma Server Action ou API Route

    3. Ação do usuário que afeta várias páginas (revalidateTag)
    ```tsx
    import { revalidateTag } from 'next/cache'

    // Adicione a tag ao fazer o fetch
    fetch(url, { next: { tags: ['posts'] } })

    // Invalide a tag após a atualização
    revalidateTag('posts')
    ```
    • Indicado para: atualizar várias páginas após uma ação do usuário
    • Uso: em uma Server Action ou API Route

    Como escolher:
    • Atualização periódica → export const revalidate
    • Uma página → revalidatePath
    • Várias páginas → revalidateTag
  3. 3

    Step 3: Investigue dados que não são atualizados

    Ordem de diagnóstico:

    1. Verifique se você está no ambiente de desenvolvimento
    • O comportamento do cache nesse ambiente não é representativo
    • Teste em produção: npm run build && npm start

    2. Verifique o Router Cache (cliente)
    • Faça uma atualização forçada (Ctrl+Shift+R)
    • Limpe o cache do navegador

    3. Verifique o Full Route Cache (servidor)
    • Invalide-o com revalidatePath
    • Confira a configuração de revalidate

    4. Verifique o Data Cache (servidor)
    • Invalide-o com revalidateTag
    • Confira a configuração cache do fetch

    5. Verifique a fonte de dados
    • Confirme se ela realmente foi atualizada
    • Confira os dados retornados pela API

    Problemas comuns:
    • Testar no ambiente de desenvolvimento → use o ambiente de produção
    • Router Cache não invalidado → faça uma atualização forçada
    • Configuração incorreta de revalidate → confira a configuração
    • Next.js 15 não armazena por padrão → configure cache explicitamente
  4. 4

    Step 4: Entenda as diferenças de cache entre Next.js 14 e 15

    Next.js 14:
    • fetch usa cache por padrão, de forma equivalente a getStaticProps
    • Para desativá-lo, é preciso definir cache: 'no-store'

    Next.js 15:
    • fetch não usa cache por padrão
    • Para ativá-lo, é preciso definir cache: 'force-cache'

    Recomendações para a migração:
    • Revise todas as chamadas fetch
    • Configure a opção cache explicitamente
    • Teste o comportamento do cache

    Exemplo:
    ```tsx
    // Next.js 14 (cache por padrão)
    fetch(url) // Armazenado automaticamente

    // Next.js 15 (sem cache por padrão)
    fetch(url, { cache: 'force-cache' }) // Exige configuração explícita
    ```

    Ponto-chave: a filosofia do Next.js 15 é que o explícito é melhor que o implícito; você precisa decidir conscientemente quais dados devem ser armazenados em cache.

FAQ

Quantas camadas de cache o Next.js tem e o que cada uma controla?
São quatro camadas:

1. Request Memoization
• Dentro da mesma renderização, uma URL idêntica gera apenas uma solicitação
• É automática e não exige configuração
• Ciclo de vida: uma única solicitação

2. Data Cache
• Cache das solicitações fetch
• Por padrão, o Next.js 14 armazena; o 15 não
• Configuração: opção cache

3. Full Route Cache
• Cache do HTML de toda a rota
• Páginas estáticas são armazenadas automaticamente
• Configuração: revalidate

4. Router Cache
• Cache usado durante a navegação no cliente
• É automático e não exige configuração
• Ciclo de vida: durante a sessão

Ponto-chave: o Router Cache fica no cliente; os outros três ficam no servidor. revalidate afeta principalmente o Data Cache e o Full Route Cache.
Qual é a diferença entre revalidatePath, revalidateTag e updateTag?
revalidatePath (uma página):
• Invalida o cache de um caminho específico
• Indicado para atualizar uma página após uma ação do usuário
• Uso: revalidatePath('/blog/post-1')

revalidateTag (várias páginas):
• Invalida todo o cache associado a uma tag
• Indicado para atualizar várias páginas após uma ação do usuário
• Uso: adicione a tag ao fetch e invalide-a após a atualização

updateTag (invalidação imediata, no Next.js 15):
• Marca a tag imediatamente como expirada
• Indicado quando a invalidação precisa ser imediata
• Uso: updateTag('tag')

Como escolher:
• Uma página → revalidatePath
• Várias páginas → revalidateTag
• Invalidação imediata → updateTag (Next.js 15)

Observação: revalidateTag e updateTag devem ser usados com as tags do fetch.
Por que os dados continuam desatualizados mesmo depois de configurar revalidate?
Possíveis causas:

1. Teste no ambiente de desenvolvimento
• O comportamento do cache não é representativo
• Use o ambiente de produção: npm run build && npm start

2. Router Cache não invalidado
• O cache do cliente ainda está ativo
• Faça uma atualização forçada (Ctrl+Shift+R)

3. Configuração incorreta de revalidate
• Confira o valor de revalidate
• Confira onde a configuração foi declarada

4. Next.js 15 não usa cache por padrão
• É preciso definir cache: 'force-cache'
• Confira a opção cache do fetch

5. A fonte de dados não foi atualizada
• Confirme se a alteração realmente ocorreu
• Confira os dados retornados pela API

Ordem de diagnóstico:
1. Ambiente de desenvolvimento
2. Router Cache (atualização forçada)
3. Full Route Cache (revalidatePath)
4. Data Cache (revalidateTag)
5. Fonte de dados
Qual é a diferença entre o cache do Next.js 14 e o do 15?
A principal diferença é:

Next.js 14:
• fetch usa cache por padrão, de forma equivalente a getStaticProps
• Para desativá-lo, use cache: 'no-store'
• Comportamento: cache implícito

Next.js 15:
• fetch não usa cache por padrão
• Para ativá-lo, use cache: 'force-cache'
• Comportamento: configuração explícita

Recomendações para a migração:
• Revise todas as chamadas fetch
• Configure a opção cache explicitamente
• Teste o comportamento do cache

Exemplo:
```tsx
// Next.js 14 (cache por padrão)
fetch(url) // Armazenado automaticamente

// Next.js 15 (sem cache por padrão)
fetch(url, { cache: 'force-cache' }) // Exige configuração explícita
```

Ponto-chave: a filosofia do Next.js 15 é que o explícito é melhor que o implícito; você precisa decidir conscientemente quais dados devem ser armazenados em cache. É uma mudança incompatível que exige atenção durante a migração.
Quando devo usar revalidatePath e quando devo usar revalidateTag?
revalidatePath (uma página):
• Indicado para atualizar uma única página após uma ação do usuário
• Exemplo: atualizar a página de detalhes depois de editar um post
• Uso: revalidatePath('/blog/post-1')

revalidateTag (várias páginas):
• Indicado para atualizar várias páginas após uma ação do usuário
• Exemplo: atualizar todas as listagens após publicar um post
• Uso: adicione a tag ao fetch e invalide-a após a atualização

Como escolher:
• Atualizar uma página → revalidatePath
• Atualizar várias páginas → revalidateTag

Exemplo:
```tsx
// Uma página
revalidatePath('/blog/post-1')

// Várias páginas
fetch(url, { next: { tags: ['posts'] } })
revalidateTag('posts') // Invalida todo cache com a tag 'posts'
```

Ponto-chave: revalidateTag deve ser usado com as tags do fetch e oferece mais flexibilidade.
O cache se comporta da mesma forma nos ambientes de desenvolvimento e produção?
Não. O comportamento do cache no ambiente de desenvolvimento não é representativo.

Desenvolvimento:
• O comportamento do cache é instável
• Pode não haver armazenamento em cache
• Não é adequado para testar cache

Produção:
• O comportamento é o esperado
• O cache funciona normalmente
• É o ambiente adequado para testes

Para testar o cache:
• Use o ambiente de produção: npm run build && npm start
• Não teste o cache no ambiente de desenvolvimento

Erros comuns:
• Testar cache em desenvolvimento → resultado impreciso
• Concluir que revalidate não funciona → o problema é o ambiente

Recomendação: sempre teste o comportamento do cache em produção.

24 min de leitura · Publicado em: 19 dez 2025 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog