Busca en todas las páginas de la documentación
Sirve un shell estático al instante y transmite contenido dinámico en límites Suspense -- lo mejor de renderizado estático y dinámico.
Tarjeta de referencia rápida -- lista para copiar y pegar.
// next.config.ts -- habilitar PPR
import type { NextConfig } from "next";
const config: NextConfig = {
experimental: {
ppr: true, // o "incremental" para optar por ruta
},
};
export default config;// app/page.tsx -- shell estático con agujeros dinámicos
import { Suspense } from "react";
import { cookies } from "next/headers";
export default function Page() {
return (
<main>
<h1>Bienvenido a la Tienda</h1> {/* Shell estático */}
Cuándo usarlo: Tienes una página donde la mayoría del contenido es estático (información de producto, texto de marketing) pero una pequeña parte depende de la solicitud (sesión del usuario, carrito, personalización).
// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
experimental: {
ppr: "incremental", // optar por ruta
},
};
export default config;// app/products/[slug]/page.tsx
import { Suspense } from "react";
import { cookies } from "next/headers";
import { db } from "@/lib/db";
import { AddToCartButton } from "./add-to-cart";
// Optar esta ruta en PPR
export const experimental_ppr = true;
Lo que esto demuestra:
experimental_ppr = truecookies() y se transmiten en tiempo de solicitudgenerateStaticParams prerrenderiza páginas de producto en tiempo de compilacióncookies(), headers(), searchParams, o cache: "no-store").Adopción incremental (optar por ruta):
// next.config.ts
export default { experimental: { ppr: "incremental" } };
// Solo esta página usa PPR
// app/products/[slug]/page.tsx
export const experimental_ppr = true;Todas las rutas PPR (global):
// next.config.ts
export default { experimental: { ppr: true } };
// Todas las rutas automáticamente usan PPRMúltiples agujeros dinámicos con fuentes de datos independientes:
export default function Page() {
return (
<main>
<StaticNav />
<StaticHero />
{/* Cada límite Suspense es un agujero dinámico independiente */}
<Suspense fallback={<UserBarSkeleton />}>
<UserBar
// Exportación de optar por ruta en PPR
export const experimental_ppr: boolean = true;
// Tipo next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
experimental: {
ppr: true | "incremental",
PPR es experimental -- A partir de Next.js 15/16, PPR requiere el flag experimental.ppr. La API puede cambiar. Solución: Fija tu versión de Next.js y prueba exhaustivamente antes del uso en producción.
La colocación del límite Suspense determina la división -- Si olvidas envolver un componente dinámico en Suspense, la página completa se vuelve dinámica. Solución: Siempre envuelve componentes que usan funciones dinámicas en un límite <Suspense>.
La calidad del fallback importa -- El fallback de Suspense es parte del shell estático y es visible instantáneamente para todos los usuarios. Un fallback pobre crea una mala primera impresión. Solución: Usa skeletons bien diseñados que coincidan con la forma y tamaño del contenido real.
Los agujeros dinámicos añaden costo de servidor -- Cada agujero dinámico requiere computación de servidor en tiempo de solicitud. Demasiados agujeros dinámicos anulan los beneficios del CDN. Solución: Consolida datos dinámicos relacionados en menos límites de Suspense.
No PPR en Edge Runtime para todos los proveedores -- PPR requiere que la plataforma de hosting soporte streaming y la API postpone. Solución: Verifica que tu plataforma de despliegue soporte PPR (Vercel lo soporta nativamente).
Los componentes de cliente en el shell estático siguen enviando JavaScript -- PPR hace el HTML estático, pero los componentes de cliente siguen hidratándose en el cliente. Solución: Mantén el shell estático compuesto principalmente de Server Components para JavaScript mínimo.
| Enfoque | Úsalo Cuando | No lo Uses Cuando |
|---|---|---|
| PPR | La mayoría de la página es estática pero algunas partes necesitan datos en tiempo de solicitud | Toda la página es completamente estática o completamente dinámica |
| Generación Completamente Estática | Toda la página puede compilarse en tiempo de despliegue | Cualquier parte necesita datos específicos del usuario |
| Renderizado Completamente Dinámico | Toda la página depende de datos en tiempo de solicitud | La mayoría del contenido es igual para todos los usuarios |
| Fetch del lado del cliente | El contenido dinámico debe cargar después de la hidratación, sin bloquear SSR | Quieres los datos dinámicos en el HTML inicial |
| ISR | El contenido cambia periódicamente pero no por solicitud | El contenido es personalizado por usuario |
| Streaming SSR (sin PPR) | Quieres renderizado progresivo sin un shell estático | Quieres que el shell se sirva desde el borde CDN |
PPR combina generación estática y renderizado dinámico en una sola ruta. Las partes estáticas se prerrenderan en tiempo de compilación y se sirven desde el CDN. Las partes dinámicas se transmiten en límites de Suspense en tiempo de solicitud.
Añade experimental: { ppr: true } (global) o experimental: { ppr: "incremental" } (optar por ruta) a next.config.ts. Para modo incremental, también exporta experimental_ppr = true en el archivo de la página.
Los límites de Suspense definen la división. Todo lo que está fuera de un límite de Suspense es parte del shell estático. Los componentes dentro de un límite de Suspense que llaman funciones dinámicas (cookies(), headers(), cache: "no-store") se vuelven agujeros dinámicos.
El fallback es parte del shell estático -- se prerrenderiza y se sirve al instante desde el CDN. Los usuarios ven el skeleton inmediatamente mientras el contenido dinámico se transmite.
Toda la página se vuelve dinámica. Sin un límite de Suspense, PPR no puede identificar la división estática/dinámica, por lo que la ruta completa se retrotrae al renderizado completamente dinámico.
Sí. PPR hace el HTML estático, pero los componentes de cliente siguen hidratándose en el cliente y enviando su JavaScript. Mantén el shell estático compuesto principalmente de Server Components para JS mínimo.
Sí. Cada límite de Suspense es un agujero dinámico independiente con su propia fuente de datos:
<Suspense fallback={<UserBarSkeleton />}>
<UserBar /> {/* cookies() */}
</Suspense>
<Suspense fallback={<PricingSkeleton />}>
<LivePricing /> {/* cache: "no-store" */}
// next.config.ts
export default { experimental: { ppr: "incremental" } };
// app/products/[slug]/page.tsx
export const experimental_ppr = true;export const experimental_ppr: boolean = true;En next.config.ts, la opción PPR se tipifica como true | "incremental" dentro del objeto experimental de NextConfig.
Sí. generateStaticParams prerrenderiza páginas de producto en tiempo de compilación como el shell estático. Los agujeros dinámicos dentro de esas páginas se transmiten en tiempo de solicitud.
PPR requiere una plataforma de hosting que soporte streaming y la API postpone. Vercel lo soporta nativamente. Verifica tu plataforma de despliegue antes de habilitar PPR en producción.
Cada agujero dinámico requiere computación de servidor en tiempo de solicitud. Demasiados agujeros dinámicos anulan los beneficios de rendimiento del CDN del shell estático. Consolida datos dinámicos relacionados en menos límites de Suspense cuando sea posible.