CategoríaCódigo y arquitectura

Renderización en Next.js: estático, incremental o dinámico

8 minutos de lectura

En Next.js, la elección entre renderizado estático, incremental y dinámico es una decisión sobre cuánto tiempo puede envejecer el dato. El estático renderiza una vez en el build. El incremental renderiza en el build y reconstruye en segundo plano tras una ventana. El dinámico renderiza en cada petición. La mayoría de los proyectos elige el dinámico por defecto y paga por eso.

Qué hace cada modo en el servidor

El renderizado decide cuándo se produce el HTML. Next.js convierte el prerenderizado en el modo por defecto para cualquier componente que no use una API de tiempo de petición, según el glosario del framework (en inglés).

  • Estático. El build produce el HTML y el payload de Server Components, y la CDN sirve ese archivo. El servidor no hace trabajo cuando llega la visita.
  • Incremental. El build prerenderiza la página, y la primera petición después de la ventana de revalidación todavía recibe la copia vieja mientras se genera una versión nueva en segundo plano. La guía de ISR (en inglés) documenta esa secuencia, desde la primera respuesta vieja hasta la actualización en caché.
  • Dinámico. El servidor renderiza la página para esa petición. Los componentes que leen cookies, headers, searchParams o params de ruta entran en este modo, como afirma la guía de caché (en inglés).

Qué descarga quien visita

Quien visita siempre descarga HTML. Lo que cambia es de dónde viene ese HTML y cuánto JavaScript llega con él.

Una página estática llega desde una máquina cerca de quien lee, así que la respuesta no espera a una base de datos. Una página dinámica llega después de que el servidor terminó su trabajo, así que la respuesta carga la latencia de la dependencia más lenta de esa página.

React afirma que los Server Components se renderizan antes del empaquetado, en un entorno separado de la app cliente. En el ejemplo de su propia documentación, las librerías usadas para renderizar markdown ni entran en el bundle. El cliente recibe la salida ya renderizada.

Eso no significa que una página estática no descargue JavaScript. Los Client Components igual envían un bundle e igual hidratan en el navegador, y la hidratación es trabajo en el hilo principal. La referencia de hidratación de React describe al cliente tomando el HTML del servidor y enlazando los manejadores de eventos.

Caché no es renderizado

En la conversación las dos cosas suelen mezclarse, y de ahí sale casi toda la confusión.

El renderizado responde cuándo se produce el HTML. La caché responde si un resultado producido se guarda y se reutiliza. La guía de caché de Next.js (en inglés) define caché como guardar el resultado de la búsqueda de datos y de otros cálculos para servir peticiones futuras más rápido, sin rehacer el trabajo.

O sea, una cosa no depende de la otra.

  • Una página dinámica puede leer de una fuente con caché, así que renderiza en cada petición sin consultar la base de datos en cada petición.
  • Una página estática puede re-renderizarse en el build y aun así no guardarse, si la capa de entrega recibe la orden de no almacenar.
  • El cliente mantiene su propia caché de navegación, separada de las dos.

Desde Next.js 16, el modelo recomendado es Cache Components (en inglés), y las apps nuevas creadas con create-next-app ya lo traen activado. La directiva use cache guarda una función de datos o un componente entero, cada uno con su propio plazo de vida. La página sobre la filosofía de renderizado (en inglés) saca la conclusión: la frontera entre estático y dinámico ahora está en el componente, no en la ruta. Una página puede entregar una base estática y transmitir las partes de tiempo de petición detrás de un límite de Suspense. Los proyectos que siguen en el modelo anterior (en inglés) todavía configuran la caché por segmento de ruta, así que conviene revisar qué modelo usa el código antes de leer su configuración.

Dónde aparecen los modos en Core Web Vitals

La vara es la de Core Web Vitals: contenido principal cargado en 2,5 segundos, respuesta a la interacción en 200 milisegundos y desplazamiento de diseño de 0,1 como máximo, leídos en el percentil 75. El post sobre framework o sitio estático explica cada métrica.

El renderizado estático mueve el trabajo al build, así que la respuesta no espera a una consulta. Eso ayuda al LCP, porque el elemento más grande puede llegar temprano. La misma página igual ejecuta hidratación, y la hidratación compite por el hilo principal, que es donde se decide el INP.

El renderizado dinámico mueve el trabajo al momento de la petición. El artículo de Web Vitals de Google nombra el tiempo de respuesta lento del servidor como causa de diagnóstico detrás de un LCP pobre. Una página que consulta tres servicios antes de renderizar hereda el más lento de los tres en cada visita.

Ninguno de los dos modos decide el CLS. La estabilidad del layout depende de si el esqueleto reserva espacio para lo que llega después, incluido el contenido que entra por streaming en un límite de Suspense.

Qué dice el Nielsen Norman Group sobre la espera

El framework decide cuándo el HTML está listo. Quien lee solo percibe la espera, y el Nielsen Norman Group mide ese lado desde hace décadas.

Los límites de tiempo de respuesta son el material más antiguo y más citado. Response Times: The 3 Important Limits (en inglés) pone 0,1 segundo como el punto en que el sistema parece instantáneo y 1,0 segundo como el límite para un flujo de razonamiento sin interrupción. Los 10 segundos aparecen como el límite para mantener la atención.

Website Response Times (en inglés) actualiza ese marco para la web. Afirma que las personas necesitan respuesta en menos de 0,1 segundo para la manipulación directa, y que un retraso de 10 segundos las hace cambiar de tarea.

Progress Indicators Make a Slow System Less Insufferable (en inglés) agrega el caso en que la espera es inevitable. El artículo afirma que un resultado que pasa de 10 segundos debe venir con una estimación, porque las personas abandonan la tarea en lugar de esperar.

Skeleton Screens 101 (en inglés) cierra el enlace con este post. Recomienda la pantalla de esqueleto para una carga de página completa de 2 a 10 segundos, que es exactamente la ventana en la que cae una ruta dinámica sin caché.

Los cuatro artículos no cuentan una sola historia sobre los mismos 10 segundos. El artículo de tiempo de respuesta trata el retraso largo como un fallo que hay que evitar. El de indicadores de progreso lo trata como un caso que se diseña con estimación. El de esqueleto queda en el medio, acepta la espera y exige que el layout reserve su espacio (NN/g, tiempos de respuesta (en inglés), NN/g, indicadores de progreso (en inglés), NN/g, skeleton screens (en inglés)).

Cuándo el dinámico es la respuesta correcta

Cuando el dato cambia para cada visitante, el renderizado dinámico no es un error. Es lo que el caso pide.

  • Cualquier cosa leída de sesión, cookie o cabecera de autorización. Next.js lista esos elementos como APIs de tiempo de petición (en inglés), que necesitan información disponible solo en el momento de la petición.
  • Precio, stock y disponibilidad que cambian en el mismo minuto.
  • Paneles autenticados y cualquier vista armada con los registros de quien lee.
  • Contenido donde una respuesta vieja es incorrecta, no solo desactualizada, como el estado de un pago o un aviso legal.

La documentación de Next.js (en inglés) es explícita en ese punto. Para componentes que necesitan dato fresco en cada petición, dice que no se use caché y que se transmitan detrás de un límite de Suspense.

El costo oculto de elegir dinámico por defecto

El costo se paga en cada visita y casi nunca aparece aparte en la factura.

  • Cada petición ejecuta el renderizado otra vez. La dependencia más lenta de la página fija el piso de la respuesta.
  • La red de borde queda casi ociosa. Un archivo estático absorbe un pico de tráfico, y un renderizado por petición no puede.
  • El cómputo se cobra por petición, así que una página que cambia una vez al día se paga miles de veces.
  • El equipo agrega caché de vuelta después, capa por capa, y reconstruye con más piezas el comportamiento que tenía en el build.

Nada de esto es desperdicio cuando el dato es de verdad por usuario. El desperdicio aparece cuando nadie nombró la ventana en la que el dato puede envejecer, y el dinámico fue el camino de menor resistencia.

Cómo tomar la decisión

Di, en segundos, cuánto tiempo puede quedar desactualizado el dato, y el modo de renderizado sale de ahí.

  • Cero segundos, o una respuesta distinta por visitante. Dinámico.
  • Una ventana medida en horas o días. Prerenderiza en el build y revalida.
  • Desconocido. Empieza estático y mide. Mover una ruta a dinámico después es un cambio de configuración, no una reescritura.

El valor por defecto del framework ayuda en ese punto. En Next.js, una página que nunca toca una API de tiempo de petición ya sale prerenderizada, y ese es el valor declarado en la documentación (en inglés).

El modo de renderizado es una afirmación sobre el dato, no sobre el framework. Elige cuánto tiempo aguanta el dato sin actualizarse, y el modo deja de ser gusto para ser consecuencia.

Fuentes

Más textos

Ver todos los textos