Fecha de publicación: 6 de febrero de 2019; última actualización: 5 de enero de 2026
Una de las decisiones principales que deben tomar los desarrolladores web es dónde implementar la lógica y la renderización en su aplicación. Esto puede ser difícil porque hay muchas maneras de crear un sitio web.
Nuestra comprensión de este espacio se basa en nuestro trabajo en Chrome, en el que hablamos con sitios grandes durante los últimos años. En términos generales, alentamos a los desarrolladores a considerar el procesamiento del servidor o el procesamiento estático en lugar de un enfoque de rehidratación completa.
Para comprender mejor las arquitecturas entre las que elegimos cuando tomamos esta decisión, necesitamos una terminología coherente y un marco de trabajo compartido para cada enfoque. Luego, podrás evaluar mejor las ventajas y desventajas de cada enfoque de renderización desde la perspectiva del rendimiento de la página.
Terminología
Primero, definiremos algunos términos que usaremos.
Renderización
- Procesamiento del servidor (SSR)
- Es la renderización de una app en el servidor para enviar HTML, en lugar de JavaScript, al cliente.
- Procesamiento del cliente (CSR)
- Renderizar una app en un navegador con JavaScript para modificar el DOM
- Renderización previa
- Ejecutar una aplicación del cliente en el momento de la compilación para capturar su estado inicial como HTML estático Ten en cuenta que la "renderización previa" en este sentido es diferente de la renderización previa del navegador de navegaciones futuras.
- Hidratación
- Ejecutar secuencias de comandos del cliente para agregar el estado y la interactividad de la aplicación al HTML renderizado por el servidor. La hidratación supone que el DOM no cambia.
- Rehidratación
- Si bien suele usarse para referirse a lo mismo que la hidratación, la rehidratación implica actualizar el DOM con regularidad con el estado más reciente, incluso después de la hidratación inicial.
Rendimiento
- Time to First Byte (TTFB)
- Es el tiempo que transcurre entre que se hace clic en un vínculo y se carga el primer byte de contenido en la página nueva.
- First Contentful Paint (FCP)
- Es el momento en que se hace visible el contenido solicitado (cuerpo del artículo, etcétera).
- Interaction to Next Paint (INP)
- Es una métrica representativa que evalúa si una página responde de forma coherente y rápida a las entradas del usuario.
- Tiempo de bloqueo total (TBT)
- Es una métrica de proxy para la INP que calcula cuánto tiempo estuvo bloqueado el subproceso principal durante la carga de la página.
Procesamiento del servidor
La renderización del servidor genera el HTML completo de una página en el servidor en respuesta a la navegación. Esto evita viajes de ida y vuelta adicionales para la recuperación de datos y la creación de plantillas en el cliente, ya que el renderizador los controla antes de que el navegador obtenga una respuesta.
Por lo general, el procesamiento del servidor produce un FCP rápido. Ejecutar la lógica de la página y la renderización en el servidor te permite evitar enviar mucho código de JavaScript al cliente. Esto ayuda a reducir el TBT de una página, lo que también puede generar un INP más bajo, ya que el subproceso principal no se bloquea con tanta frecuencia durante la carga de la página. Cuando el subproceso principal se bloquea con menos frecuencia, las interacciones del usuario tienen más oportunidades de ejecutarse antes.
Esto tiene sentido, ya que, con la renderización del servidor, solo envías texto y vínculos al navegador del usuario. Este enfoque puede funcionar bien para una variedad de condiciones de red y dispositivos, y abre interesantes optimizaciones del navegador, como el análisis de documentos de transmisión.
Con la renderización del servidor, es menos probable que los usuarios tengan que esperar a que se ejecute JavaScript vinculado a la CPU antes de poder usar tu sitio. Incluso cuando no puedes evitar el JavaScript de terceros, usar la renderización del servidor para reducir tus propios costos de JavaScript propios puede darte más presupuesto para el resto. Sin embargo, existe una posible desventaja con este enfoque: generar páginas en el servidor lleva tiempo, lo que puede aumentar el TTFB de tu página.
Si la renderización del servidor es suficiente para tu aplicación, depende en gran medida del tipo de experiencia que estés creando. Existe un debate de larga data sobre las aplicaciones correctas de la renderización del servidor en comparación con la renderización del cliente, pero siempre puedes elegir usar la renderización del servidor para algunas páginas y no para otras. Algunos sitios adoptaron técnicas de renderización híbrida con éxito. Por ejemplo, Netflix renderiza en el servidor sus páginas de destino relativamente estáticas, mientras que prefetching el código JavaScript para las páginas con mucha interacción, lo que les da a estas páginas más pesadas renderizadas por el cliente una mejor oportunidad de cargarse rápidamente.
Con muchos frameworks, bibliotecas y arquitecturas modernos, puedes renderizar la misma aplicación tanto en el cliente como en el servidor. Puedes usar estas técnicas para la renderización del servidor. Sin embargo, las arquitecturas en las que la renderización se produce tanto en el servidor como en el cliente son su propia clase de solución con características de rendimiento y compensaciones muy diferentes. Los usuarios de React pueden usar las APIs de DOM del servidor o las soluciones basadas en ellas, como Next.js, para la renderización del servidor. Los usuarios de Vue pueden usar la guía de renderización del servidor de Vue o Nuxt. Angular tiene Universal.
Sin embargo, la mayoría de las soluciones populares usan algún tipo de hidratación, así que ten en cuenta los enfoques que usa tu herramienta.
Renderización estática
La renderización estática ocurre en el momento de la compilación. Este enfoque ofrece un FCP rápido, así como un TBT y un INP más bajos, siempre que limites la cantidad de JavaScript del cliente en tus páginas. A diferencia de la renderización del servidor, también logra un TTFB rápido de forma constante, ya que el HTML de una página no se tiene que generar de forma dinámica en el servidor. En general, la renderización estática implica producir un archivo HTML separado para cada URL con anticipación. Con las respuestas HTML generadas con anticipación, puedes implementar renderizaciones estáticas en varias CDN para aprovechar el almacenamiento en caché perimetral.
Las soluciones para la renderización estática vienen en todas las formas y tamaños. Las herramientas como Gatsby están diseñadas para que los desarrolladores sientan que su aplicación se renderiza de forma dinámica, no que se genera como un paso de compilación. Las herramientas de generación de sitios estáticos, como 11ty, Jekyll y Metalsmith, adoptan su naturaleza estática y proporcionan un enfoque más basado en plantillas.
Una de las desventajas de la renderización estática es que debe generar archivos HTML individuales para cada URL posible. Esto puede ser difícil o incluso imposible cuando necesitas predecir esas URLs con anticipación y para sitios con una gran cantidad de páginas únicas.
Los usuarios de React pueden estar familiarizados con Gatsby, la exportación estática de Next.js o Navi, que facilitan la creación de páginas a partir de componentes. Sin embargo, la renderización estática y la renderización previa se comportan de manera diferente: las páginas renderizadas de forma estática son interactivas sin necesidad de ejecutar mucho JavaScript del cliente, mientras que la renderización previa mejora el FCP de una aplicación de una sola página que se debe iniciar en el cliente para que las páginas sean verdaderamente interactivas.
Si no sabes con certeza si una solución determinada es de renderización estática o de renderización previa, intenta inhabilitar JavaScript y cargar la página que quieres probar. En el caso de las páginas renderizadas de forma estática, la mayoría de las funciones interactivas siguen existiendo sin JavaScript. Es posible que las páginas renderizadas previamente aún tengan algunas funciones básicas, como vínculos con JavaScript inhabilitado, pero la mayor parte de la página está inerte.
Otra prueba útil es usar la limitación de la red en las Herramientas para desarrolladores de Chrome y ver cuánto JavaScript se descarga antes de que una página se vuelva interactiva. Por lo general, la renderización previa necesita más JavaScript para volverse interactiva, y ese JavaScript suele ser más complejo que el enfoque de mejora progresiva que se usa en la renderización estática.
Procesamiento del servidor frente al procesamiento estático
La renderización del servidor no es la mejor solución para todo, ya que su
naturaleza dinámica puede tener costos de sobrecarga de procesamiento significativos. Muchas soluciones de renderización del servidor no se vacían antes, retrasan el TTFB o duplican los datos que se envían (por ejemplo, los estados intercalados que usa JavaScript en el cliente). En React,
renderToString() puede ser lento porque es síncrono y de un solo subproceso.
Las APIs de DOM del servidor de React más recientes admiten la transmisión, que puede llevar la parte inicial de una respuesta HTML al navegador antes, mientras que el resto se sigue generando en el servidor.
Para lograr una renderización del servidor "correcta", puede ser necesario encontrar o compilar una solución para el almacenamiento en caché de componentes, administrar el consumo de memoria, usar técnicas de memorización y otras cuestiones. A menudo, procesas o vuelves a compilar la misma app dos veces, una en el cliente y otra en el servidor. La renderización del servidor que muestra el contenido antes no necesariamente te da menos trabajo. Si tienes mucho trabajo en el cliente después de que llega una respuesta HTML generada por el servidor, esto puede generar un TBT y un INP más altos para tu sitio web.
La renderización del servidor produce HTML a pedido para cada URL, pero puede ser más lenta que solo entregar contenido renderizado estático. Si puedes realizar el trabajo adicional, la renderización del servidor junto con el almacenamiento en caché de HTML pueden reducir significativamente el tiempo de renderización del servidor. La ventaja de la renderización del servidor es la capacidad de extraer más datos "en vivo" y responder a un conjunto más completo de solicitudes que lo que es posible con la renderización estática. Las páginas que necesitan personalización son un ejemplo concreto del tipo de solicitud que no funciona bien con la renderización estática.
La renderización del servidor también puede presentar decisiones interesantes cuando se compila una PWA. ¿Es mejor usar el almacenamiento en caché del service worker de página completa o renderizar en el servidor partes individuales del contenido?
Renderización del cliente
La renderización del cliente implica renderizar páginas directamente en el navegador con JavaScript. Toda la lógica, la recuperación de datos, la creación de plantillas y el enrutamiento se controlan en el cliente en lugar de en el servidor. El resultado efectivo es que se pasan más datos al dispositivo del usuario desde el servidor, y eso conlleva su propio conjunto de compensaciones.
La renderización del cliente puede ser difícil de crear y mantener rápida para los dispositivos móviles.
Con un poco de trabajo para mantener un presupuesto de JavaScript ajustado
y ofrecer valor en la menor cantidad posible de ida y vuelta,
puedes lograr que la renderización del cliente casi replique
el rendimiento de la renderización pura del servidor. Puedes hacer que el analizador funcione
más rápido si entregas secuencias de comandos y datos críticos con <link rel=preload>
También recomendamos considerar el uso de patrones como PRPL
para garantizar que las navegaciones iniciales y posteriores se sientan instantáneas.
La principal desventaja de la renderización del cliente es que la cantidad de JavaScript necesaria tiende a aumentar a medida que crece una aplicación, lo que puede afectar el INP de una página. Esto se vuelve especialmente difícil con la incorporación de nuevas bibliotecas de JavaScript, polyfills y código de terceros, que compiten por la potencia de procesamiento y, a menudo, se deben procesar antes de que se pueda renderizar el contenido de una página.
Las experiencias que usan la renderización del cliente y dependen de grandes paquetes de JavaScript deberían considerar la división de código agresiva para reducir el TBT y el INP durante la carga de la página, así como la carga diferida de JavaScript para entregar solo lo que el usuario necesita, cuando lo necesita. Para las experiencias con poca o ninguna interactividad, la renderización del servidor puede representar una solución más escalable para estos problemas.
Para quienes compilan aplicaciones de una sola página, identificar las partes principales de la interfaz de usuario que comparten la mayoría de las páginas te permite aplicar la técnica de almacenamiento en caché de la shell de la aplicación. En combinación con los service workers, esto puede mejorar drásticamente
el rendimiento percibido en las visitas repetidas, ya que la página puede cargar su
shell de la aplicación HTML y las dependencias de CacheStorage con mucha rapidez.
La rehidratación combina la renderización del servidor y del cliente
La hidratación es un enfoque que mitiga las compensaciones entre la renderización del cliente y del servidor realizando ambas. Las solicitudes de navegación, como las cargas o recargas de páginas completas, las controla un servidor que renderiza la aplicación en HTML. Luego, el JavaScript y los datos que se usan para la renderización se incorporan en el documento resultante. Cuando se hace con cuidado, se logra un FCP rápido, como la renderización del servidor, y, luego, se "retoma" volviendo a renderizar en el cliente.
Esta es una solución eficaz, pero puede tener desventajas de rendimiento considerables.
La principal desventaja de la renderización del servidor con rehidratación es que puede tener un impacto negativo significativo en el TBT y el INP, incluso si mejora el FCP. Las páginas renderizadas del servidor pueden parecer cargadas y ser interactivas, pero no pueden responder a las entradas hasta que se ejecuten las secuencias de comandos del cliente para los componentes y se adjunten los controladores de eventos. En dispositivos móviles, esto puede tardar minutos, lo que confunde y frustra al usuario.
Un problema de rehidratación: una app por el precio de dos
Para que el JavaScript del cliente continúe con precisión donde lo dejó el servidor, sin volver a solicitar todos los datos con los que el servidor renderizó su HTML, la mayoría de las soluciones de renderización del servidor serializan la respuesta de las dependencias de datos de una IU como etiquetas de secuencia de comandos en el documento. Debido a que esto duplica mucho código HTML, la rehidratación puede causar más problemas que solo la interactividad retrasada.
El servidor devuelve una descripción de la IU de la aplicación en respuesta a una
solicitud de navegación, pero también devuelve los datos de origen que se usaron para componer esa
IU y una copia completa de la implementación de la IU que, luego, se inicia en el
cliente. La IU no se vuelve interactiva hasta que bundle.js termina de cargarse y ejecutarse.
Las métricas de rendimiento recopiladas de sitios web reales que usan la renderización del servidor y la rehidratación indican que rara vez es la mejor opción. El motivo más importante es su efecto en la experiencia del usuario, cuando una página parece lista, pero ninguna de sus funciones interactivas funciona.
Hay esperanza para la renderización del servidor con rehidratación. A corto plazo, solo usar la renderización del servidor para el contenido que se puede almacenar en caché puede reducir el TTFB, lo que produce resultados similares a la renderización previa. La rehidratación incremental, progresiva o parcial podría ser la clave para que esta técnica sea más viable en el futuro.
Transmite la renderización del servidor y rehidrata de forma progresiva
La renderización del servidor tuvo varios desarrollos en los últimos años.
La renderización del servidor de transmisión
te permite enviar HTML en fragmentos que el navegador puede renderizar de forma progresiva a medida que se
recibe. Esto puede hacer que el lenguaje de marcado llegue más rápido a los usuarios, lo que acelera el FCP. En
React, los flujos son asíncronos en renderToPipeableStream(), en comparación con
los síncronos renderToString(), lo que significa que la contrapresión se controla bien.
También vale la pena considerar la rehidratación progresiva (React la implementó). Con este enfoque, las partes individuales de una aplicación renderizada por el servidor se "inician" con el tiempo, en lugar del enfoque común actual de inicializar toda la aplicación a la vez. Esto puede ayudar a reducir la cantidad de JavaScript necesaria para que las páginas sean interactivas, ya que te permite aplazar la actualización del cliente de las partes de baja prioridad de la página para evitar que bloquee el subproceso principal, lo que permite que las interacciones del usuario se produzcan antes de que el usuario las inicie.
La rehidratación progresiva también puede ayudarte a evitar uno de los errores más comunes de la rehidratación de renderización del servidor: un árbol del DOM renderizado por el servidor se destruye y, luego, se vuelve a compilar de inmediato, con mayor frecuencia porque la renderización inicial síncrona del cliente requería datos que no estaban listos, a menudo una Promise que aún no se resolvió.
Rehidratación parcial
La rehidratación parcial resultó difícil de implementar. Este enfoque es una extensión de la rehidratación progresiva que analiza partes individuales de la página (componentes, vistas o árboles) y, luego, identifica las partes con poca interactividad o sin reactividad. Para cada una de estas partes mayormente estáticas, el código de JavaScript correspondiente se transforma en referencias inertes y funciones decorativas, lo que reduce su huella del cliente a casi cero.
El enfoque de rehidratación parcial tiene sus propios problemas y desventajas. Plantea algunos desafíos interesantes para el almacenamiento en caché, y la navegación del cliente implica que no podemos suponer que el HTML renderizado por el servidor para las partes inertes de la aplicación esté disponible sin una carga de página completa.
Renderización trisomórfica
Si los service workers son una opción para ti, considera la renderización trisomórfica. Esta técnica te permite usar la renderización del servidor de transmisión para las navegaciones iniciales o sin JavaScript y, luego, hacer que tu service worker se encargue de renderizar el código HTML para las navegaciones después de que se haya instalado. Esto puede mantener actualizados los componentes y las plantillas almacenados en caché, y habilitar las navegaciones de estilo SPA para renderizar vistas nuevas en la misma sesión. Este enfoque funciona mejor cuando puedes compartir el mismo código de plantillas y enrutamiento entre el servidor, la página del cliente y el service worker.
Consideraciones de SEO
Cuando eligen una estrategia de renderización web, los equipos suelen considerar el impacto del SEO. La renderización del servidor es una opción popular para ofrecer una experiencia de "aspecto completo" que los rastreadores puedan interpretar. Los rastreadores pueden comprender JavaScript, pero suele haber limitaciones en la forma en que lo renderizan. La renderización del cliente puede funcionar, pero suele requerir pruebas y sobrecarga adicionales. Más recientemente, la renderización dinámica también se convirtió en una opción que vale la pena considerar si tu arquitectura depende en gran medida de JavaScript del cliente.
Conclusión
Cuando decidas qué enfoque de renderización usar, mide y comprende cuáles son tus cuellos de botella. Considera si el procesamiento estático o el procesamiento del servidor pueden ayudarte a lograr la mayor parte de tu objetivo. Está bien enviar principalmente HTML con un mínimo de JavaScript para obtener una experiencia interactiva. Aquí tienes una infografía útil que muestra el espectro de servidor-cliente:
Créditos
Gracias a todos por sus opiniones y su inspiración:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson y Sebastian Markbåge.