Como desarrolladores de software, nos solemos enfrentar con decisiones en nuestros proyectos que afectarán a toda la arquitectura de estos de una u otra manera. Una de las decisiones principales que deben de tomarse en el planteamiento de un proyecto web es dónde implementar la lógica y el renderizado del frontend. Esta decisión puede no ser tan evidente a veces y debemos analizar nuestro escenario para encontrar la estrategia más adecuada para renderizar nuestra web.
En el último año, ha cogido gran fuerza la estrategia JAMstack (https://jamstack.org/). Generalmente esta aproximación contempla una web únicamente desarrollada con JavaScript, APIs y lenguaje de marcado. Estos sitios suelen construirse con generadores de sitios estáticos como pueden ser Jekyll, Hugo, Nuxt, Next, Gatsby, entre otros.
Lo que todos estos sites tienen en común es que no dependen de un backend que está renderizando la web en cada request, liberándose así de costes de infraestructura. Otra característica que representa a estos sites JAMStack es, generalmente, la utilización de Headless CMS (https://headlesscms.org/) para recuperar la fuente de datos estáticos en el momento de build.
Para comprender mejor el tipo de arquitectura que podemos elegir cuando tomamos este tipo de decisión, necesitamos tener un fuerte conocimiento de cada enfoque, identificar cuáles son sus ventajas y desventajas y trabajar con una terminología coherente para identificarlos correctamente.
Las diferencias entre cada uno de estos enfoques de renderizando ayudan a tomar la decisión acertada para tu arquitectura, sobre todo vista desde el punto de vista del performance y del coste de la infraestructura. Descubriremos cómo elegir la mejor estrategia desde un modelo tradicional como SSR hasta un modelo más innovador como CSR.
Estrategias de renderizado web
Para ello, lo primero que necesitamos entender son las siglas/nomenclaturas que utilizamos para identificar inequívocamente cada estrategia de renderizado:
- SSR: Server-Side Rendering: se renderiza el HTML del cliente totalmente en el lado servidor. Esta aproximación suele ser el modelo de webs tradicionales.
- CSR: Client-Side Rendering: se renderiza la aplicación completamente en el navegador haciendo uso de Javascript y la gestión del DOM. Comúnmente conocidas como Single Page Applications.
- Rehydration: esta estrategia es un híbrido de las dos anteriores en el que se aprovecha el HTML y los datos renderizados desde el lado servidor, hidratándose con una aplicación javascript que se monta encima de esta.
- Prerendering: en esta estrategia se renderiza la web en tiempo de construcción creando un artefacto que puede servirse de una manera totalmente estática.
Glosario de métricas sobre el performance
Existen una serie de métricas de performance que son muy importantes para analizar cada una de las estrategias de renderizado anteriores y que vamos a ver cómo se relacionan con cada uno de ellos.
- TTFB: Time to First Byte. El tiempo entre que pinchas un enlace y el primer byte de contenido llega al navegador.
- FP: First Paint. Es el tiempo que tarda en pintarse el primer píxel que haga visible la web al usuario.
- FCP: First Contentful Paint. Es el tiempo que tarda en ser visible el contenido que hemos solicitado.
- TTI: Time To Interactive. Es el tiempo que tarda una página en ser interactiva.
SSR: Server Side Rendering
Server Side Rendering es una estrategia en la que se genera el HTML completo para una página en el servidor en respuesta a una url y su navegación. Esto evita viajes de ida y vuelta adicionales para la obtención de datos y la creación de plantillas en el cliente, ya que se maneja antes de que el navegador obtenga una respuesta. Suele ser el modelo de webs tradicionales y el renderizado de CMSs clásicos como WordPress o Drupal
Esta estrategia produce un FP (First Paint) y un FCP (First Contentful Paint) muy rápido. La ejecución de la lógica de página y la representación en el servidor hace posible evitar el envío de mucho JavaScript al cliente, lo que ayuda a lograr un tiempo de interacción (TTI) rápido, ya que no requieren de tanta interacción, aunque no siempre es así.
Con la respuesta del servidor en realidad solo está enviando texto y enlaces al navegador del usuario, sin embargo la construcción de este estado global lo convierte a veces en un proceso pesado de renderización que perjudica radicalmente al Time to First Byte.
Estos problemas de rendimiento se suelen solventar con algún tipo de software de aceleramiento HTTP como puede ser Varnish o Nginx. También algunos frameworks dan soluciones para implementar una implementación caché HTTP como puede ser Symfony con su componente Http Cache Component. (https://symfony.com/doc/current/http_cache.html)
CSR: Client Side Rendering
Client Side Rendering es una estrategia en la que se genera el HTML de las páginas completamente en el lado cliente apoyándose en JavaScript. Toda la responsabilidad sobre la obtención de datos, la creación de templates o el enrutamiento se llevan al lado cliente en lugar del servidor. Estas aplicaciones suelen ser comúnmente conocidas como Single Page Applications (SPA) y su origen nace a raíz del auge de frameworks Javascript como React, Angular o Vue.
CSR puede tener algunas desventajas en la carga para dispositivos móviles, ya que estas aplicaciones requieren de la construcción en el lado cliente por lo que hace un uso intensivo del navegador. La cantidad de Javascript tiende a crecer a medida que crece la aplicación, por lo que cuanto más grande es nuestra aplicación más lenta se puede volver. Sin embargo, para solventar estos problemas podemos considerar estrategias como code-splitting o lazy load: partiendo nuestro build de tal manera que “servimos solo lo que necesita, cuando se necesita”.
Este tipo de estrategias son ideales para aplicaciones privadas como paneles de administración, aplicaciones corporativas, intranets… donde la necesidad es que la interacción sea buena y fluida. Para mejorar el rendimiento se apoyan en soluciones de caché en el lado cliente como Local Storage y Service Workers, para ofrecer una buena experiencia de usuario.
Estas soluciones suelen tener un rápido TTFB, ya que sirven un HTML delgado y un Javascript generalmente cacheado por el navegador que construye completamente nuestra aplicación. Esta construcción a veces genera un TTI alto, ya que se requiere de un tiempo de carga inicial que puede demorarse unos segundos, hasta que nuestra aplicación esté lista y con los datos necesarios para arrancar.
Rehidratación: Entre el CSR y el SSR
La rehidratación es una técnica de renderizado híbrido, la cual intenta posicionarse en el medio de SSR y CSR, aprovechando lo mejor de cada mundo.
Las peticiones son manejadas por una capa servidor que genera el HTML del servidor como en el modelo SSR, obteniendo las ventajas de una primera renderización rápida y completa de la web, satisfaciendo requisitos SEO. Posteriormente en el lado cliente, la capa Javascript se encarga de recuperar el renderizado y los datos siendo a partir de ese momento el responsable del renderizado con una técnica que se conoce como rehidratación.
Algunos frameworks y herramientas modernas hacen posible esta estrategia que permite renderizar la misma aplicación tanto en el lado cliente como en el lado servidor. Los desarrolladores de React pueden usar renderToString() o soluciones creadas encima, como Next.js para la representación del servidor. Los desarrolladores de Vue también cuentan con su homólogo Nuxt, y Angular cuenta con su tecnología Universal.
El principal inconveniente de esta estrategia es que puede tener un impacto negativo significativo en TTI. A pesar de mejorar considerablemente el FP, este tipo de renderizado a menudo se ve engañoso ya que parece cargado e interactivo, pero en realidad no puede responder a las entradas de eventos hasta que se ejecuta completamente el javascript del lado del cliente. Este proceso puede llevar algunos segundos en dispositivos móviles.
Sin embargo, aprovechando estrategias como la hidratación progresiva, que permite rehidratar componentes aprovechando la API de Intersection Observer, se puede optimizar y paliar bastante los problemas en el tiempo de interacción.
Prerendering
El prerendering o renderizado estático ocurre en la etapa de build. Esta estrategia se encarga de producir un archivo HTML separado para cada URL en el momento de construcción del site. Estos HTML pregenerados, se pueden alojar en CDNs para aprovechar el almacenamiento en caché y servir a gran velocidad una versión completa del website, sin depender de una infraestructura pesada.
Esta estrategia ofrece un FP y FPC muy rápido, lo que ha provocado una gran acogida, estos tiempos son beneficiosos para la experiencia de usuario y el SEO. A semejanza de soluciones como SSR, también logra alcanzar un tiempo de carga muy rápido (TTFB), ya que el HTML de una página no tiene que generarse al vuelo, si no que ya se encuentra preconstruido. En este escenario el TTI es variable y depende de la cantidad de JS del lado del cliente
Existen multitud de soluciones para el renderizado estático que ha aparecido en los últimos años. Uno de los más conocidos es GatsbyJS, el cual permite sentir a los desarrolladores que la aplicación se procesa dinámicamente en lugar de generarse como un paso de compilación. Sin embargo, existen multitud de soluciones como NextJS, Hugo o Jekyll que también ofrecen implementar este tipo de sites.
Una de las desventajas del renderizado estático es que se deben generar archivos HTML individuales para cada URL. Esto puede ser a veces un desafío o incluso inviable cuando es difícil predecir cuáles serán todo el mapa de URLs con anticipación, o para sitios con una gran cantidad de páginas únicas y con contenido cambiante.
Sin embargo los puntos principales por los que esta estrategia se ha puesto de moda:
- Alto Rendimiento: las páginas no se construyen al vuelo, sino en la etapa de build, por lo que se minimiza el TTFB y los ficheros reconstruidos se sirven a través de una red global CD.
- Alta seguridad: con esta aproximación el lado servidor se puede abstraer reduciendo las áreas de ataque
- Barato y escalable: los despliegues son muy sencillos, ya que solo hay que servir un build estáticos de ficheros html, css y Javascript, además más barato porque solo necesitamos alojarlo en un CDN, que requiere menos coste que una aproximación que requiere de cómputo para el renderizado.
Jamstack en la vida real: Gatsbylius
Gatsbylius es una iniciativa open-source que une un generador de sites estático con un framework PHP para construir e-commerces. Esta tecnología une lo dinámico de un e-commerce y lo estático de un generador, para conseguir que la experiencia web de un site de comercio electrónico sea exquisita.
Esta solución se vende como la solución e-commerce más rápida apoyándose en hombros de gigantes como Gatsby y Sylius. y ofreciendo una arquitectura desacoplada con un headless e-commerce y basado en un frontend en React.
Este tipo de soluciones están marcando tendencia y delatan que JAMstack ha llegado para quedarse y ser una opción para distintos tipos de soluciones.
Consideraciones SEO
Los desarrolladores a menudo tenemos en cuenta el impacto SEO al elegir una estrategia para renderizar nuestras webs. Las soluciones que pasan por el renderizado por servidor que ofrece una experiencia de aspecto completo, ayudan a los rastreadores a interpretar e indexar con facilidad en los buscadores. Actualmente muchos rastreadores son capaces de entender Javascript, pero existen limitaciones, que rompen una lanza en favor a soluciones como SSR o Prerendering para cumplir con estos requisitos, por ello es un factor muy importante a tener en cuenta en la arquitectura de nuestras aplicaciones, más allá del propio performance.
Conclusión
Al tratar de decidir un enfoque para el renderizado, debemos de analizar y comprender cuáles son los requisitos y las necesidades del proyecto. Debemos de buscar el equilibrio para encontrar qué estrategia nos compensa a medio/largo plazo. También, nunca hay que apostar por una sola estrategia como cual bala de plata, sino que podemos partir de distintos frontales en función de las necesidades. Por ejemplo un site con landings estáticos, un blog de contenido tradicional y una PWA CSR.
Una vez elegida una estrategia, debemos de tener muy en cuenta sus características para tratar de paliar sus inconvenientes principales, utilizando estrategias de medición constante con herramientas como Lighthouse.
Recursos
https://lemoncode.net/lemoncode-blog/2018/7/6/server-side-rendering-ii-implementacion-react-next
https://lemoncode.net/lemoncode-blog/2018/5/13/server-side-rendering-i-conceptos
https://developers.google.com/web/tools/puppeteer/articles/ssr
https://altalogy.com/blog/client-side-rendering-vs-server-side-rendering/