Cómo usar el Renderizado Parcial (Partial Prerendering)

El Renderizado Parcial (PPR) es una estrategia de renderizado que te permite combinar contenido estático y dinámico en la misma ruta. Esto mejora el rendimiento inicial de la página mientras sigue admitiendo datos personalizados y dinámicos.

Página de producto con Renderizado Parcial mostrando navegación estática e información del producto, junto con carrito dinámico y productos recomendados

Cuando un usuario visita una ruta:

  • El servidor envía un shell que contiene el contenido estático, garantizando una carga inicial rápida.
  • El shell deja huecos para el contenido dinámico que se cargará de forma asíncrona.
  • Los huecos dinámicos se transmiten en paralelo, reduciendo el tiempo total de carga de la página.

🎥 Ver: Por qué PPR y cómo funciona → YouTube (10 minutos).

¿Cómo funciona el Renderizado Parcial?

Para entender el Renderizado Parcial, es útil familiarizarse con las estrategias de renderizado disponibles en Next.js.

Renderizado Estático

Con el Renderizado Estático, el HTML se genera con anticipación, ya sea en el momento de la construcción o mediante revalidación. El resultado se almacena en caché y se comparte entre usuarios y solicitudes.

En el Renderizado Parcial, Next.js prerrenderiza un shell estático para una ruta. Esto puede incluir el diseño y cualquier otro componente que no dependa de datos en tiempo de solicitud.

Renderizado Dinámico

Con el Renderizado Dinámico, el HTML se genera en tiempo de solicitud. Esto te permite servir contenido personalizado basado en datos en tiempo de solicitud.

Un componente se vuelve dinámico si utiliza las siguientes APIs:

En el Renderizado Parcial, el uso de estas APIs lanza un error especial de React que informa a Next.js que el componente no se puede renderizar estáticamente, causando un error de construcción. Puedes usar un límite de Suspense para envolver tu componente y diferir el renderizado hasta el tiempo de ejecución.

Suspense

React Suspense se utiliza para diferir el renderizado de partes de tu aplicación hasta que se cumpla alguna condición.

En el Renderizado Parcial, Suspense se utiliza para marcar límites dinámicos en tu árbol de componentes.

En el momento de construcción, Next.js prerrenderiza el contenido estático y la UI de fallback. El contenido dinámico se pospone hasta que el usuario solicite la ruta.

Envolver un componente en Suspense no hace que el componente en sí sea dinámico (eso lo determina tu uso de APIs), sino que Suspense actúa como un límite que encapsula contenido dinámico y habilita el streaming.

app/page.js
import { Suspense } from 'react'
import StaticComponent from './StaticComponent'
import DynamicComponent from './DynamicComponent'
import Fallback from './Fallback'

export const experimental_ppr = true

export default function Page() {
  return (
    <>
      <StaticComponent />
      <Suspense fallback={<Fallback />}>
        <DynamicComponent />
      </Suspense>
    </>
  )
}

Streaming

El streaming divide la ruta en fragmentos y los transmite progresivamente al cliente a medida que están listos. Esto permite que el usuario vea partes de la página inmediatamente, antes de que todo el contenido haya terminado de renderizarse.

Diagrama que muestra una página parcialmente renderizada en el cliente, con UI de carga para fragmentos que se están transmitiendo.

En el Renderizado Parcial, los componentes dinámicos envueltos en Suspense comienzan a transmitirse desde el servidor en paralelo.

Diagrama que muestra la paralelización de segmentos de ruta durante el streaming, mostrando la obtención de datos, renderizado e hidratación de fragmentos individuales.

Para reducir la sobrecarga de red, la respuesta completa, incluyendo el HTML estático y las partes dinámicas transmitidas, se envía en una única solicitud HTTP. Esto evita viajes adicionales y mejora tanto la carga inicial como el rendimiento general.

Habilitar el Renderizado Parcial

Puedes habilitar PPR añadiendo la opción ppr a tu archivo next.config.ts:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}

export default nextConfig

El valor 'incremental' te permite adoptar PPR para rutas específicas:

/app/dashboard/layout.tsx
export const experimental_ppr = true

export default function Layout({ children }: { children: React.ReactNode }) {
  // ...
}
/app/dashboard/layout.js
export const experimental_ppr = true

export default function Layout({ children }) {
  // ...
}

Las rutas que no tengan experimental_ppr tendrán como valor predeterminado false y no se prerrenderizarán usando PPR. Debes optar explícitamente por PPR para cada ruta.

Nota importante:

  • experimental_ppr se aplicará a todos los hijos del segmento de ruta, incluyendo diseños y páginas anidadas. No es necesario añadirlo a cada archivo, solo al segmento superior de una ruta.
  • Para deshabilitar PPR en segmentos hijos, puedes establecer experimental_ppr en false en el segmento hijo.

Ejemplos

APIs Dinámicas

Cuando se usan APIs dinámicas que requieren examinar la solicitud entrante, Next.js optará por el renderizado dinámico para la ruta. Para seguir usando PPR, envuelve el componente con Suspense. Por ejemplo, el componente <User /> es dinámico porque usa la API cookies:

import { cookies } from 'next/headers'

export async function User() {
  const session = (await cookies()).get('session')?.value
  return '...'
}

El componente <User /> se transmitirá mientras que cualquier otro contenido dentro de <Page /> se prerrenderizará y formará parte del shell estático.

import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'

export const experimental_ppr = true

export default function Page() {
  return (
    <section>
      <h1>Esto se prerrenderizará</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  )
}

Pasar props dinámicas

Los componentes solo optan por el renderizado dinámico cuando se accede al valor. Por ejemplo, si estás leyendo searchParams desde un componente <Page />, puedes reenviar este valor a otro componente como prop:

import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'

export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  return (
    <section>
      <h1>Esto se prerrenderizará</h1>
      <Suspense fallback={<TableSkeleton />}>
        <Table searchParams={searchParams} />
      </Suspense>
    </section>
  )
}

Dentro del componente de tabla, acceder al valor de searchParams hará que el componente sea dinámico mientras que el resto de la página se prerrenderizará.

export async function Table({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  const sort = (await searchParams).sort === 'true'
  return '...'
}

On this page