ISR em Escala: Executando 25.000+ Páginas com Incremental Static Regeneration no Vercel

No ano passado, lançamos um site Next.js com mais de 25.000 páginas geradas estaticamente no Vercel. Páginas de produtos, posts de blog, páginas de localização, filtros de categoria dinâmicos -- tudo incluído. A promessa da Incremental Static Regeneration é sedutora: obter a velocidade de sites estáticos com a atualização de conteúdo renderizado no servidor. E honestamente? Funciona muito bem. Mas com 25.000+ páginas, o ISR se comporta diferentemente de como funciona em seu site de marketing com 50 páginas. Os casos extremos se tornam seus casos principais. Os custos aumentam gradualmente. Os problemas de invalidação de cache que pareciam teóricos na documentação se tornam muito, muito reais.

Este é o artigo que eu gostaria que existisse antes de começarmos. Tudo aqui vem de experiência em produção -- métricas reais, surpresas de faturamento reais e decisões arquiteturais reais que tomamos (e às vezes lamentamos).

ISR em Escala: Executando 25.000+ Páginas com Incremental Static Regeneration no Vercel

Índice

O que o ISR Realmente Faz Nos Bastidores

Antes de entrar em problemas de escala, vamos nos certificar de que estamos na mesma página sobre o que o ISR está fazendo. Quando você define revalidate: 60 em uma página Next.js, aqui está o fluxo real:

  1. Primeira requisição após deploy: Se a página foi pré-renderizada no tempo de build, o Vercel a serve do cache de borda. Se não (você retornou fallback: 'blocking' ou usou dynamicParams: true no App Router), ela é renderizada no servidor, o resultado é armazenado em cache e então servido.

  2. Requisições subsequentes dentro da janela de revalidação: Servidas do cache. Rápido. Sem computação.

  3. Primeira requisição após a janela de revalidação expirar: A página antiga é servida imediatamente (esta é a parte "stale-while-revalidate"), e uma regeneração em background é acionada. O próximo visitante obtém a página fresca.

Este é conceitualmente simples. Mas com 25.000 páginas, essa etapa de regeneração em background se torna um fluxo contínuo.

// App Router (Next.js 14/15)
export const revalidate = 60; // segundos

export async function generateStaticParams() {
  // Com 25k páginas, você provavelmente não quer retornar todas aqui
  const topPages = await getTop500Pages();
  return topPages.map((page) => ({ slug: page.slug }));
}

export default async function ProductPage({ params }: { params: { slug: string } }) {
  const product = await getProduct(params.slug);
  return <ProductTemplate product={product} />;
}

O Trade-off Stale-While-Revalidate

A coisa que causa confusão nas pessoas: ISR sempre serve conteúdo antigo para a requisição que aciona a regeneração. Esta é uma funcionalidade, não um bug -- significa que nenhum visitante jamais espera por uma renderização. Mas também significa que seu conteúdo está sempre pelo menos uma requisição atrás. Para um site de 25.000 páginas onde algumas páginas são visitadas uma vez por semana, esse "uma requisição atrás" pode significar que alguém vê conteúdo com dias de idade após a janela de revalidação passar, porque ninguém visitou para acionar a regeneração.

Por Que 25.000 Páginas Mudam Tudo

Em pequena escala, ISR é basicamente mágica. Em grande escala, três coisas mudam:

Tempos de Build Se Tornam um Gargalo

Se você tentar pré-renderizar todas as 25.000 páginas no tempo de build, você está olhando para tempos de build que o farão questionar suas escolhas de vida. Cada página precisa buscar seus dados, renderizar React para HTML e gerar os ativos estáticos. Mesmo em 200ms por página (o que é otimista se você estiver acionando uma API de CMS), são 5.000 segundos -- mais de 83 minutos. O plano Pro do Vercel tem um timeout de build de 45 minutos. Enterprise oferece mais, mas você ainda está queimando créditos de computação.

Invalidação de Cache Se Torna um Problema Real

Com 25.000 páginas, você não pode apenas "reconstruir tudo" quando o conteúdo muda. Você precisa de invalidação cirúrgica. As APIs revalidatePath() e revalidateTag() do Vercel ajudam, mas têm suas próprias peculiaridades em escala que cobriremos.

Picos de Carga de Regeneração em Background

Imagine 5.000 páginas tendo todas revalidate: 60 e recebendo tráfego simultaneamente. São 5.000 invocações de função serverless acontecendo em background a cada minuto. Sua API de CMS melhor estar preparada para isso.

ISR em Escala: Executando 25.000+ Páginas com Incremental Static Regeneration no Vercel - arquitetura

Estratégia de Build: O que Pré-renderizar vs. Adiar

Esta é a decisão arquitetural mais importante para sites ISR grandes. Aqui está o framework que usamos:

Categoria de Página Contagem (Nosso Caso) Estratégia Raciocínio
Páginas de alto tráfego (top 500) 500 Pré-renderizar no build Estas são acionadas imediatamente após deploy. Sem penalidade de cold-start.
Páginas de tráfego médio 4.500 Adiar com fallback: 'blocking' Primeiro visitante espera ~300ms, depois é armazenado em cache. Aceitável.
Páginas de cauda longa 20.000 Adiar com fallback: 'blocking' A maioria não será visitada por horas/dias após deploy. Não faz sentido pré-renderizar.

O insight chave: não pré-renderize páginas que ninguém vai visitar na primeira hora após deploy. Você está desperdiçando minutos de build e dinheiro.

// generateStaticParams - retorna apenas suas páginas de alto tráfego
export async function generateStaticParams() {
  // Usamos dados de analytics para determinar as páginas principais
  const topPages = await fetch('https://api.example.com/pages/top?limit=500', {
    headers: { Authorization: `Bearer ${process.env.CMS_TOKEN}` },
  }).then(r => r.json());

  return topPages.map((page: { slug: string }) => ({
    slug: page.slug,
  }));
}

Com essa abordagem, nossos builds foram de timeout para conclusão em cerca de 8 minutos. Essa é uma diferença enorme. Escrevemos sobre estratégias de otimização semelhantes no contexto do nosso trabalho de desenvolvimento Next.js -- os princípios se aplicam amplamente.

A Configuração `dynamicParams` Importa

No App Router, definir dynamicParams = true (o padrão) significa que páginas não retornadas por generateStaticParams serão renderizadas sob demanda e armazenadas em cache. Defini-lo como false retorna um 404 para qualquer página não pré-renderizada. Para um site de 25.000 páginas, você quase certamente quer true.

export const dynamicParams = true; // Permite renderização sob demanda para páginas não em generateStaticParams

Padrões de Revalidação Que Realmente Funcionam

Revalidação Baseada em Tempo

A abordagem mais simples. Defina revalidate para um número de segundos. Mas qual número?

Aqui está no que chegamos após meses de ajuste:

Tipo de Conteúdo Período de Revalidação Por Que
Preços de produtos 60 segundos Preços mudam frequentemente, clientes percebem preços antigos
Descrições de produtos 3600 segundos (1 hora) Raramente muda, não é sensível ao tempo
Posts de blog 86400 segundos (24 horas) Quase nunca muda após publicação
Páginas de categoria/listagem 300 segundos (5 minutos) Novos produtos aparecem, mas um pequeno atraso é OK
Páginas de localização 86400 segundos (24 horas) Informações de endereço praticamente não mudam

O erro que cometemos no início: definir tudo para 60 segundos. Isso martelou nossa API de CMS (Contentful, no nosso caso) com requisições de regeneração e atingimos limites de taxa durante picos de tráfego.

Revalidação Sob Demanda

Esta é a abordagem melhor para a maioria das atualizações de conteúdo. Em vez de fazer polling com revalidação baseada em tempo, você aciona a regeneração quando o conteúdo realmente muda:

// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-revalidation-secret');
  
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
  }

  const body = await request.json();
  
  // Revalidação baseada em tag -- este é o caminho
  if (body.tag) {
    revalidateTag(body.tag);
    return NextResponse.json({ revalidated: true, tag: body.tag });
  }

  // Revalidação baseada em caminho como fallback
  if (body.path) {
    revalidatePath(body.path);
    return NextResponse.json({ revalidated: true, path: body.path });
  }

  return NextResponse.json({ error: 'No tag or path provided' }, { status: 400 });
}

Em seguida, configure um webhook no seu CMS para acionar este endpoint sempre que conteúdo é publicado. Combinamos isso com uma revalidação baseada em tempo mais longa (como 24 horas) como rede de segurança.

Revalidação Baseada em Tag em Escala

É aqui que Next.js 14+ realmente brilha para sites grandes. Você pode marcar suas requisições de fetch e invalidar por tag:

async function getProduct(slug: string) {
  const res = await fetch(`https://api.cms.com/products/${slug}`, {
    next: { 
      tags: [`product-${slug}`, 'products', 'all-content'],
      revalidate: 86400 // rede de segurança de 24 horas
    },
  });
  return res.json();
}

Agora quando um único produto é atualizado, você chama revalidateTag('product-blue-widget') e apenas essa página é regenerada. Quando você faz uma atualização em massa de preços, chama revalidateTag('products') e todas as páginas de produtos são regeneradas na próxima visita.

A armadilha: chamar revalidateTag('products') em um site com 25.000 páginas de produtos não regenera todas imediatamente. Marca todas como antigas. Elas são regeneradas na próxima visita. Isto é importante -- significa que algumas páginas podem não se atualizar de verdade por dias se tiverem pouco tráfego.

Armadilhas Específicas do Vercel e Limites

Estamos executando isso no Vercel desde o início de 2024. Aqui estão as coisas que a documentação não enfatiza o suficiente:

Armazenamento em Cache de ISR

O Vercel armazena páginas ISR no cache da Edge Network. A partir de 2025, o Vercel Data Cache tem alguns limites que você deve conhecer:

  • Plano Pro: Cache ISR incluído é generoso, mas há um custo para leituras/escritas de cache em volume muito alto
  • Enterprise: Limites personalizados, mas você está pagando por isso
  • Entradas de cache não vivem para sempre: Mesmo com revalidate: false, o Vercel pode despejar entradas de cache que não foram acessadas recentemente. Vimos páginas desaparecerem do cache após cerca de 30 dias sem tráfego no plano Pro.

Duração de Função Serverless

A regeneração em background é executada como uma função serverless. No Vercel Pro, o timeout padrão é 60 segundos (você pode configurar até 300 segundos). Se sua página leva mais tempo para regenerar -- por exemplo, porque seu CMS é lento ou você está fazendo processamento pesado de imagens -- a regeneração falha silenciosamente e a página antiga continua sendo servida.

Atingimos isso com páginas que buscavam dados de três APIs diferentes. A solução foi adicionar uma camada de cache (Redis via Upstash) entre nossa app Next.js e a API mais lenta.

Limites de Regeneração Concorrente

O Vercel não publica números rígidos sobre isso, mas observamos throttling quando mais de ~1.000 regenerações de ISR foram acionadas simultaneamente (por exemplo, após chamar revalidateTag em uma tag amplamente usada). As regenerações ficam na fila e são processadas ao longo de vários minutos em vez de todas de uma vez. Planeje para isso.

Cold Starts

Páginas que não foram visitadas há um tempo (e foram despejadas do cache de borda) sofrerão um cold start na próxima visita. Em nossos benchmarks:

  • Acerto de cache quente: 15-40ms TTFB
  • Revalidação antiga (servida do cache): 15-40ms TTFB (igual, já que antiga é servida)
  • Regeneração fria (sem cache, bloqueando): 400-1200ms TTFB dependendo dos tempos de resposta da API

Custos Reais de Produção em Escala

Vamos falar sobre dinheiro. É aqui que as pessoas se surpreendem.

Nosso site de 25.000 páginas no Vercel Pro ($20/mês base) com ISR:

Componente de Custo Mensal Notas
Assinatura Vercel Pro $20 Plano base
Execução de Função Serverless $180-$340 Varia com o tráfego. Regenerações de ISR contam como invocações de função.
Bandwidth de Borda $90-$150 25k páginas com imagens se acumula
Cache de Dados Vercel $40-$80 Leituras/escritas de cache para ISR
Total Vercel $330-$590/mês Depende do tráfego do mês
Contentful (CMS) $489/mês Seu plano Team. Chamadas de API a partir da regeneração de ISR nos levaram rapidamente acima do tier gratuito.
Upstash Redis (cache) $30/mês Adicionado para reduzir chamadas de API de CMS
Total Geral $849-$1.109/mês Para um site servindo ~2M pageviews/mês

É caro? Comparado a uma configuração de servidor tradicional, é competitivo. Comparado a um site estático em um CDN, é caro. As invocações de função de regeneração de ISR são o maior custo variável -- toda vez que uma página é regenerada, essa é uma função serverless executando por 1-5 segundos.

Trabalhamos com clientes que exploram abordagens baseadas em Astro para sites com muito conteúdo onde os custos do ISR começam a superar seus benefícios. Para sites onde o conteúdo muda com pouca frequência, uma build estática completa com Astro pode ser significativamente mais barata de hospedar.

Monitoramento e Debug de ISR em Produção

Falhas de ISR são silenciosas por padrão. A página antiga continua sendo servida, e você pode não saber que sua regeneração vem falhando há dias. Aqui está nossa configuração de monitoramento:

Logging de Regeneração Customizado

// lib/with-regeneration-logging.ts
export async function fetchWithLogging(
  url: string,
  options: RequestInit & { next?: { tags?: string[]; revalidate?: number } }
) {
  const start = Date.now();
  try {
    const res = await fetch(url, options);
    const duration = Date.now() - start;
    
    // Log para seu serviço de monitoramento
    if (duration > 5000) {
      console.warn(`[ISR] Slow fetch: ${url} took ${duration}ms`);
      // Enviar para Datadog/Sentry/etc.
    }
    
    return res;
  } catch (error) {
    console.error(`[ISR] Fetch failed: ${url}`, error);
    // Isso é crítico -- se fetch falhar, regeneração falha
    throw error;
  }
}

Ferramentas Built-in do Vercel

O dashboard do Vercel mostra taxas de acerto de cache de ISR e contagens de regeneração. Na aba Analytics, procure por:

  • Status de cache nos logs de função: HIT, MISS, STALE
  • Duração de regeneração de ISR nas métricas de função serverless
  • Taxas de erro em suas rotas ISR

O Header `x-vercel-cache`

Cada resposta do Vercel inclui este header:

  • HIT -- Servido do cache de borda, fresco
  • STALE -- Servido do cache de borda, regeneração acionada em background
  • MISS -- Não em cache, renderizado sob demanda

Configuramos um monitor simples que verifica 100 páginas aleatórias a cada hora e alerta se mais de 10% retornar MISS -- isto indicaria problemas de despejo de cache.

Decisões de Arquitetura: ISR vs. Alternativas

Após executar ISR nesta escala por mais de um ano, aqui está minha opinião honesta sobre quando usá-lo e quando não:

Use ISR Quando:

  • Você tem 5.000-100.000 páginas que mudam em frequências diferentes
  • Atualização de conteúdo medida em minutos (não segundos) é aceitável
  • Você já está comprometido com Next.js
  • Sua equipe entende invalidação de cache (não é conhecimento opcional nesta escala)

Considere Alternativas Quando:

  • Você precisa de conteúdo em tempo real (use SSR ou busca no cliente)
  • Seu site raramente muda (builds estáticos completos são mais simples e baratos)
  • Você tem 500.000+ páginas (ISR começa a sobrecarregar em contagens de página muito altas -- considere uma abordagem de build distribuída)
  • Custo é a preocupação principal (Next.js auto-hospedado com seu próprio CDN pode ser 60-70% mais barato)

Para clientes com arquiteturas de conteúdo complexas, frequentemente recomendamos uma configuração de CMS headless que oferece flexibilidade para alternar entre ISR, SSR e estático completo dependendo do tipo de conteúdo.

A Abordagem Híbrida Que Realmente Usamos

Não usamos ISR para tudo em nosso site de 25k páginas. Aqui está o detalhamento:

  • ISR: Páginas de produtos, páginas de categoria, páginas de localização (22.000 páginas)
  • SSR: Resultados de busca, dashboard do usuário, carrinho
  • Estática: Sobre, contato, páginas legais (geradas no tempo de build, sem revalidação)
  • Cliente: Contagens de inventário em tempo real, preços específicos do usuário

Essa abordagem híbrida reduziu nossos custos de função serverless em cerca de 40% em comparação com nossa estratégia inicial de "ISR tudo".

Benchmarks de Desempenho da Nossa Implantação

Aqui estão números reais de nossa implantação em produção, medidos ao longo de Q1 2025:

Métrica Acerto de Cache ISR Falha de Cache ISR (Bloqueando) SSR Completo (Sem Cache)
TTFB (p50) 22ms 480ms 620ms
TTFB (p95) 58ms 1.100ms 1.450ms
TTFB (p99) 120ms 2.800ms 3.200ms
LCP (p50) 1.1s 1.8s 2.2s
CLS 0.02 0.02 0.05
Taxa de Aprovação de Core Web Vitals 96% 78% 64%

A diferença entre um acerto de cache e uma falha é dramática. É por isso que sua estratégia de pré-renderização é tão importante -- você quer que suas páginas de alto tráfego estejam sempre aquecidas.

Uma descoberta interessante: nossos scores de Core Web Vitals melhoraram 12% quando passamos de revalidate: 60 para revalidate: 3600 em conteúdo de baixa alteração. Menos regenerações significava mais acertos de cache consistentes, o que significava desempenho mais consistente.

FAQ

Quantas páginas o ISR pode manipular no Vercel antes do desempenho se degradar?

Executamos 25.000 páginas sem problemas significativos, e ouvi falar de implantações com 100.000+ páginas funcionando bem. O gargalo não é o número de páginas em cache -- é a taxa de regenerações simultâneas. Se você tiver 50.000 páginas com revalidate: 60, terá problemas. Espalhe seus períodos de revalidação com base na frequência de alteração de conteúdo e você ficará bem.

ISR custa mais do que SSR no Vercel?

Geralmente, ISR é significativamente mais barato que SSR para o mesmo volume de tráfego. Com ISR, a maioria das requisições é servida do cache de borda (essencialmente sem computação). Com SSR, cada requisição executa uma função serverless. Para nosso site de 2M pageviews/mês, as invocações de função de ISR (de regenerações) foram aproximadamente 15% do que SSR completo teria sido.

O que acontece quando a regeneração de ISR falha?

A versão antiga continua a ser servida. Esta é tanto uma funcionalidade quanto um risco. Seus usuários não veem erros, mas podem ver conteúdo desatualizado. Tivemos situações onde uma interrupção de API de CMS significava que as páginas estavam servindo conteúdo com 6 horas de idade antes de alguém perceber. Configure monitoramento.

Posso usar ISR com o App Router do Next.js?

Sim, e é na verdade mais limpo no App Router. Você usa export const revalidate = 60 no nível da página ou layout, e next: { revalidate, tags } em suas chamadas de fetch. A função generateStaticParams substitui getStaticPaths. Tudo o que descrevemos neste artigo funciona com Pages Router e App Router, embora a sintaxe do App Router seja o que recomendaríamos para novos projetos em 2025.

Como lidar com ISR com parâmetros de query dinâmicos?

ISR apenas armazena em cache baseado no caminho da URL, não em parâmetros de query. Se você precisa de versões em cache diferentes para ?color=red vs ?color=blue, você precisa usar segmentos de caminho reais (/product/widget/red em vez de /product/widget?color=red) ou manipular a variação no cliente. Isto nos apanhou desprevenidos com nossa implementação de filtragem.

A revalidação sob demanda é confiável em escala?

Principalmente. Observamos ocasionais atrasos de 10-30 segundos entre chamar revalidateTag() e o cache realmente ser invalidado em todos os locais de borda. Para 99% dos casos de uso isto é bom. Se você precisar de invalidação global instantânea, você pode precisar adicionar parâmetros de cache-busting ou usar SSR para essas páginas específicas.

Devo auto-hospedar Next.js em vez de usar Vercel para sites ISR grandes?

Depende da sua equipe. Auto-hospedagem (em AWS, por exemplo) oferece mais controle sobre o comportamento de cache e pode ser 50-70% mais barato em escala. Mas você é responsável por configurar a invalidação de cache de CDN, gerenciar o pipeline de build e lidar com distribuição de borda você mesmo. Vimos equipes gastando meses replicando o que o Vercel oferece pronto para uso. Se você quer explorar opções, entre em contato -- fizemos ambos.

Qual é o melhor CMS para um site ISR de 25.000+ páginas?

Usamos Contentful, Sanity e Hygraph nesta escala. Contentful manipula revalidação baseada em webhook bem, mas rate limits podem ser um problema (planeje para cache). As subscrições GROQ do Sanity são ótimas para consciência em tempo real de alterações de conteúdo. O sistema de webhook do Hygraph é sólido. O requisito chave é entrega confiável de webhook e uma API que possa manipular tráfego em explosão de regeneração. Verifique nossas capacidades de desenvolvimento de CMS headless para recomendações mais específicas com base em seu modelo de conteúdo.