Static Generation
HTML at build time. CDN cached. Fastest TTFB possible. ✗ Stale user data. No personalization.
O Partial Prerendering no Next.js 16 resolve o clássico tradeoff de renderização, entregando velocidade estática com dados dinâmicos em uma resposta.

A escolha agonizante entre páginas estáticas ultrarrápidas e conteúdo dinâmico em tempo real definiu por muito tempo a experiência de desenvolvimento no Next.js. Mas e se você nunca mais precisasse fazer concessões? Com o PPR, o dilema entre estático versus dinâmico está finalmente obsoleto.
Considere uma página de produto em um site de e-commerce. O título, a descrição e as imagens quase nunca mudam — eles devem ser estáticos, armazenados em cache na borda da CDN e servidos em milissegundos. A contagem do carrinho do usuário, suas recomendações personalizadas e o estoque em tempo real — esses mudam por requisição e por usuário. Eles precisam ser dinâmicos.
Antes do PPR, você precisava escolher um modo para toda a rota. Se a tornasse estática, o carrinho estaria sempre desatualizado. Se a tornasse SSR, cada usuário pagaria o custo total da renderização no servidor — mesmo para conteúdo que não mudou desde o último deploy.O PPR permite que você tenha ambos, na mesma rota, na mesma resposta HTTP.
HTML at build time. CDN cached. Fastest TTFB possible. ✗ Stale user data. No personalization.
Fresh data on every request. Full personalization. ✗ Slow TTFB. No CDN. Every user pays the cost.
Sends chunks as they're ready. Progressive loading. ✗ Entire page is dynamic. No static CDN shell.
Static shell from CDN. Dynamic parts streamed in the same response. ✓ Edge TTFB + fresh data. Best of all strategies.
PPR is not a new rendering strategy. It's the end of the rendering strategy decision tree.
O modelo mental é mais simples do que parece. Uma regra, um limite, uma resposta.
At build time, Next.js renders everything it can — navigation, layout, headings, product descriptions, images, footers. Anything that doesn't depend on the incoming request. This becomes the static shell: a complete HTML document with Suspense fallback placeholders where the dynamic parts will go. The shell is stored at the CDN edge.
When a user requests the page, the CDN sends the cached shell immediately — that's your TTFB. In parallel, the origin server renders only the dynamic parts: the user's cart, their recommendations, real-time stock. These stream into the same HTTP response, filling the Suspense holes as they resolve. No second request. No client-side fetch. One response, two types of content.
Everything outside a <Suspense> boundary is static — prerendered at build time, cached at the edge. Everything inside is dynamic — rendered at request time and streamed. The Suspense boundary is not just a loading UI: in PPR, it's the architectural boundary that separates static from dynamic.
In Next.js 16, all code is dynamic by default. You opt into static caching using the "use cache" directive or Suspense structure. This is the inverse of previous versions where pages were static by default. The experimental.ppr flag is gone — replaced by cacheComponents: true.
Uma flag de configuração. Essa é toda a configuração necessária para desbloquear o Partial Prerendering para sua aplicação.
next.config.ts — enable Cache Components (Next.js 16)// next.config.ts — Next.js 16+import type { NextConfig } from 'next';const nextConfig: NextConfig = { // Enables Partial Prerendering + Cache Components cacheComponents: true,};export default nextConfig;// ⚠️ If you're on Next.js 14 or 15, use the old experimental flag:// const nextConfig = { experimental: { ppr: 'incremental' } }// The experimental.ppr flag was REMOVED in Next.js 16app/products/[id]/page.tsx — static shell + dynamic holesimport { Suspense } from 'react';import { ProductDetails } from './product-details'; // staticimport { UserCart } from './user-cart'; // dynamicimport { Recommendations } from './recommendations'; // dynamicimport { CartSkeleton } from './skeletons';import { RecommendationsSkeleton } from './skeletons';// This page uses PPR — shell is prerendered, holes are streamedexport default async function ProductPage({ params,}: { params: { id: string };}) { return ( <main> {/* ✅ STATIC SHELL ProductDetails only uses params.id — known at build time Next.js prerenders this and caches it at the CDN edge */} <ProductDetails productId={params.id} /> {/* ✅ DYNAMIC HOLE #1 — reads cookies() inside Wrapped in Suspense → streams at request time CartSkeleton shows immediately while UserCart loads */} <Suspense fallback={<CartSkeleton />}> <UserCart /> </Suspense> {/* ✅ DYNAMIC HOLE #2 — personalized per user Streams in parallel with UserCart — no waterfall */} <Suspense fallback={<RecommendationsSkeleton />}> <Recommendations productId={params.id} /> </Suspense> </main> );}user-cart.tsx — dynamic component (uses request-time API)// app/products/[id]/user-cart.tsximport { cookies } from 'next/headers'; // ← makes this component dynamicexport async function UserCart() { const sessionToken = (await cookies()).get('session')?.value; if (!sessionToken) { return <div>Sign in to see your cart</div>; } const cart = await fetchCart(sessionToken); // fetch user-specific data return ( <div className="cart"> <span>{cart.itemCount} items</span> <span>${cart.total}</span> </div> );}// Why is this dynamic?// cookies() accesses the incoming HTTP request — unknown at build time.// Any component that calls cookies(), headers(), or connection()// is automatically dynamic in Next.js 16.// PPR streams it in after the static shell is served.Além dos limites de Suspense, o Next.js 16 introduziu a "use cache" diretiva — uma maneira de marcar funções ou componentes individuais como armazenáveis em cache no nível do servidor.
"use cache" — component-level caching// Cache an individual async function — not the whole componentasync function getProductData(productId: string) { 'use cache'; // ← this function's result is cached const data = await db.products.findUnique({ where: { id: productId } }); return data;}// Cache a component directlyasync function ProductHeader({ productId }: { productId: string }) { 'use cache'; // ← this component is cached at build time const product = await getProductData(productId); return ( <header> <h1>{product.title}</h1> <p>{product.description}</p> </header> );}// The key insight in Next.js 16:// "use cache" = explicit opt-in to static caching// No directive = dynamic by default// Suspense boundary = where dynamic content streams in//// Together, "use cache" + Suspense = Partial PrerenderingO ◐ símbolo no output do build é como você confirma que o PPR está funcionando para uma rota.
Route (app) Size First Load JS◐ /products/[id] 4.2 kB 89 kB○ /about 1.1 kB 82 kB● /blog/[slug] 2.3 kB 84 kBλ /dashboard 3.1 kB 86 kB○ Static ● SSG ◐ Partial Prerendering λ Dynamic (SSR)A maioria das rotas SSR pode se tornar rotas PPR com mudanças mínimas. O segredo é identificar quais partes precisam de dados em tempo de requisição e envolvê-las em Suspense.
// The whole page waits for user data// Even the static header is blockedexport default async function Page() { // ❌ All of this blocks the entire page const user = await getUser(); // auth check const cart = await getCart(user); // user-specific const recs = await getRecs(user); // personalized const product = await getProduct(); // static data // Nothing ships until ALL of these resolve return ( <main> <Header /> <ProductDetails product={product} /> <CartWidget cart={cart} /> <Recommendations recs={recs} /> </main> );}// Static content ships immediately from CDN// Dynamic content streams in parallelexport default async function Page({ params }) { // ✅ Only static data fetched at the page level const product = await getProduct(params.id); return ( <main> {/* ✅ Static — in the prerendered shell /} <Header /> <ProductDetails product={product} /> {/ ✅ Dynamic — streamed at request time /} <Suspense fallback={<CartSkeleton />}> <CartWidget /> {/ fetches user + cart inside /} </Suspense> <Suspense fallback={<RecsSkeleton />}> <Recommendations id={params.id} /> {/ personalized */} </Suspense> </main> );}O PPR não é uma bala de prata. Existe uma categoria de conteúdo onde o PPR pode, na verdade, prejudicá-lo — e é fácil deixar passar despercebido durante o desenvolvimento.
Tipo de conteúdo | Colocar no shell estático | Manter no Suspense (dinâmico) |
|---|---|---|
Título do produto, descrição, imagens | ✓ Shell estático | — |
Navegação, layout, rodapé | ✓ Shell estático | — |
Conteúdo de blog, documentação | ✓ Shell estático | — |
Carrinho do usuário, lista de desejos | ✗ Nunca estático | ✓ Transmitir via Suspense |
Recomendações personalizadas | ✗ Nunca estático | ✓ Transmitir via Suspense |
Preço do produto | ✗ Evitar — risco de desatualização | ✓ Transmitir se mudar |
Contagem de estoque em tempo real | ✗ Nunca estático | ✓ Transmitir via Suspense |
Estado de autenticação | ✗ Nunca estático | ✓ Transmitir via Suspense |
Variante de teste A/B | ⚠ Apenas se tolerar desatualização | ✓ Preferir Suspense |
Essas não são estratégias concorrentes. Elas são camadas. O PPR funciona melhor quando 60%+ da sua página é estática.
Estratégia | TTFB | Dados atualizados | Personalizado | Usar quando |
|---|---|---|---|---|
SSG | Velocidade na borda | Apenas no tempo de build | Não | Páginas de marketing, docs — zero conteúdo dinâmico |
ISR | Velocidade na borda | Revalidado | Não | Blog, catálogo — muda previsivelmente em um cronograma |
PPR | Velocidade na borda | Por requisição | Sim (transmitido) | 60%+ layout estático + dados dinâmicos do usuário — e-commerce, dashboards com shell público |
SSR | Latência da origem | Por requisição | Sim | Cada byte é específico do usuário — painéis administrativos, dashboards financeiros |
CSR | Shell instantâneo | Fetch do cliente | Sim | UI altamente interativa atrás de autenticação — mapas, editores, apps complexos |
- Páginas de produto: conteúdo estático + barra lateral personalizada, carrinho, recomendações
- Artigos de notícias ou blog com comentários, anúncios específicos do usuário ou progresso de leitura
- Landing pages com elementos testados em A/B ou CTAs específicos do usuário
- Dashboard com um shell público e widgets específicos do usuário
- 60%+ do conteúdo da página não muda por usuário
SEO importa — o shell estático é imediatamente rastreável
- Cada byte é específico do usuário (dados financeiros, painéis administrativos) → SSR
- O conteúdo é 100% estático e nunca personaliza → SSG ou ISR
- Preços ou estoque estão na área acima da dobra (risco de desatualização) → SSR
- Você está no Pages Router — o PPR requer o App Router
- Você precisa de consistência total entre o shell e o conteúdo dinâmico o tempo todo
O PPR não substitui SSR, SSG, ISR ou CSR. Ele coordena com eles. A árvore de decisão de renderização que definiu o desenvolvimento no Next.js por anos — "esta página é estática ou dinâmica?" — agora é uma decisão de nível de componente, não de nível de rota.
A mudança arquitetural é real: no Next.js 16, você para de pensar nas páginas como tendo um único modo de renderização. Você começa a pensar em quais partes desta página são estáticas e quais partes precisam ser atualizadas. Suspense é seu limite. O lado estático vai para a borda da CDN e é enviado com latência de borda. O lado dinâmico é transmitido da origem na mesma resposta HTTP.
O resultado prático é que a maioria dos sites de conteúdo, páginas de e-commerce e dashboards com shell público e dados específicos do usuário tornam-se mais rápidos sem qualquer custo de SSR. A página que seu usuário vê é o mesmo dado — apenas chega em uma ordem mais inteligente.
O único ponto para ter cuidado: nunca coloque conteúdo no shell estático sobre o qual os usuários possam agir antes que a atualização dinâmica chegue. Preços desatualizados, estoque desatualizado, descontos desatualizados no shell enquanto a transmissão ainda está carregando — é aí que o PPR pode prejudicar a conversão em vez de ajudar.
nextjs.org/blog/next-16 (Out 2025) — Notas de lançamento oficiais do Next.js 16: Cache Components, PPR estável, cacheComponents: true, React Compiler estável, Turbopack estável.
nextjs.org/docs/app/guides/ppr-platform-guide (Mar 2026) — Guia de plataforma: shell estático, postponedState, protocolo de resumo de CDN, arquitetura de streaming.
nextjs.org/docs/15/app/getting-started/partial-prerendering (Ago 2025) — Guia de introdução: experimental_ppr, limites de Suspense, "use cache" diretiva.
PageGlass (27 de abr de 2026) — "Partial Prerendering: Um Guia Prático (2026)" — PPR vs streaming SSR, cache de CDN, requisito do App Router, regra 60/30.
TheCodeForge (11 de abr de 2026) — "Partial Prerendering no Next.js 16" — armadilhas de produção, estudo de caso de queda de 12% na conversão, matriz de estratégia, composição PPR + ISR.
DEV Community / Pockit (18 de mar de 2026) — "Mergulho Profundo no PPR do Next.js" — ◐ indicador de output de build, depuração de páginas totalmente dinâmicas, padrões de migração.
Ashish Gogula (4 de dez de 2025) — "Um Guia Prático para PPR no Next.js 16" — cacheComponents: true config, projeto de demonstração, padrão Suspense + esqueleto.
U11D (13 de fev de 2026) — "Next.js 16 PPR: O Melhor da Renderização Estática e Dinâmica" — caso de uso de e-commerce, "use cache" diretiva, comparação com SSG/ISR.
Exemplos de código refletem a API estável do Next.js 16 (outubro de 2025). A flag experimental.ppr e o experimental_ppr export de rota foram removidos no Next.js 16 — não os use em novos projetos. cacheComponents: true é a API estável. O PPR requer o App Router — não está disponível no Pages Router. Sempre verifique o comportamento com seu provedor de CDN, pois o comportamento de cache na borda para rotas PPR varia por plataforma.