Server Side Rendering

<< Integrar librerias Animaciones >>

Angular Server Side Rendering permite la ejecución de Angular en el servidor para renderizar el HTML antes de devolverle la aplicacion (HTML, JavaScript, CSS) al cliente (navegador). Esto tiene ciertas ventajas:

  • Rendimiento a la hora de mostrar la primera página, es decir, el navegador puede renderizar el contenido (ya que lo recibe dentro del index.html) antes de descargar y ejectuar los archivos JavaScript, y por lo tanto Angular. De cara al SEO, esto es muy positivo ya que mejora el LCP ( Largest Contentful Paint).
  • Los motores de búsqueda que no son capaces de ejecutar Angular, pueden indexar el contenido del sitio ya que lo genera el servidor. Los enlaces de la página se comportan como enlaces normales que piden al servidor la nueva página. También sirve para que puedan visitar la web navegadores con JavaScript deshabilitado, aunque con funcionalidad limitada.

Una vez se descarga el HTML, CSS y JavaScript, si el cliente lo soporta, se ejecuta Angular y este toma el control de la aplicación, por lo que funcionaría como una aplicación Angular normal. En estos casos SSR nos proporciona una manera rápida de cargar el contenido de la primera página que queremos visualizar.

Activar SSR

Cuando creas un proyecto, Angular te da la opción de activar SSR. Si no se activa en este momento, siempre se puede hacer con el siguiente comando:

ng add @angular/ssr

Esto crea una aplicación express en el archivo server.ts (raíz del proyecto) y una configuración de la aplicación específica para el servidor en src/app/app.config.ts.

Aquí se puede ver como para todas las rutas, la aplicación express ejecuta Angular y devuelve el HTML resultante:

Qué no se puede hacer en el servidor

En el lado del servidor no podemos acceder a elementos o APIs específicas del navegador como pueden ser window, document, navigator, location, localStorage, etc. Angular provee algunos servicios de abstraccion como pueden ser DOCUMENT o Location, que podemos inyectar y usar en lugar de los nativos. De esta manera en el servidor en lugar de provocar un error, funcionarán internamente de otra manera. Angular SSR utiliza domino en el servidor para crear una representación interna del DOM.

Para controlar las variaciones de código entre cliente y servidor, podemos inyectar PLATFORM_ID que nos permite saber en combinación con la función isPlatformBrowser si el código se está ejecutando en servidor o en cliente. Por ejemplo, la API de LocalStorage no estará disponible en servidor.

Otra opción para ejecutar código específico de cliente, sobre todo aquel que tiene que ver con manipulación del DOM que solo funciona en el navegador es utilizar las funciones afterRender o afterNextRender. Lo que pongamos ahí solo se ejecutará después de renderizar el DOM en el navegador (cliente). Sería similar a ngAfterViewInit pero en este caso, no se ejecuta en el servidor.

Gestionar páginas con autenticación

Podemos simplemente no gestionar correctamente la autenticación en el servidor como acabamos de ver en el interceptor, ya que no tenemos acceso a la API de LocalStorage en este contexto. Aparentemente la aplicación funcionaría correctamente ya que en el servidor, al no tener token, la lógica de nuestra aplicación nos enviaría a la página de login, y esta es la que se envía al cliente.

Sin embargo, en el cliente, una vez Angular toma el control, se vuelven a comprobar los guards, etc. Por lo que inmediatamente habría una redirección a la página correcta donde queríamos ir en un principio. Esto produce un efecto de parpadeo o flickering donde durante unos milisegundos se visualiza la página de login, para inmediatamente pasar a la página correcta.

Uso de cookies

Al ejecutarse el servidor donde tengamos la aplicación Angular SSR en el dominio de la aplicación cliente, podemos utilizar cookies. Cosa que no tiene por qué ocurrir con el back-end, que puede estar ejecutándose en un dominio diferente de la aplicación cliente.

La librería ngx-cookie-service nos permite crear y acceder a las cookies de forma transparente, independientemente de si la aplicación se está ejecutando en el servidor o en el navegador. La instalamos con el siguiente comando:

npm i ngx-cookie-service-ssr

Para la parte del servidor, en el archivo server.ts, debemos buscar el array providers (commonEngine.render) y añadirle lo siguiente:

En la aplicación simplemente debemos inyectar el servicio SsrCookieService y ya podemos crear y leer cookies.

De esta manera, si tenemos un token guardado, el servidor rederizará la página privada en lugar de la página de login. Al ejecutarse Angular en el cliente seguirá en la misma página y no habrá ningún efecto extraño.

Hidratación (hydration)

La hidratación o hydration es un proceso mediante el cual la aplicación una vez se ejecuta en el lado del cliente (navegador), es capaz de reutilizar los datos obtenidos en el servidor (http), así como las estructuras DOM creadas por el mismo, en lugar de volver a recrearlas otra vez. Esto implica un mejor rendimiento percibido por el usuario, sobre todo en la interacción inicial con la aplicación, mejorando mucho la métrica conocida como First Input Delay (FID).

Por defecto, al activar SSR en la aplicación, se activa también la hidratación mediante la llamada a provideClientHydration en el array providers de app.config.ts.

Importante: Se deben tener en cuenta la siguientes reglas si queremos que este proceso funcione correctamente y Angular pueda aprovechar la estructura HTML renderizada en el servidor en lugar de recrearla de cero:

  • Se debe generar exactamente la misma estructura DOM en el servidor y en el cliente. No se pueden hacer distinciones y generar algo diferente en el servidor.
  • No puede haber un proceso o servicio intermedio que altere el HTML entre el servidor y el cliente.
  • Las plantillas de los componentes deben de tener una estructura HTML válida. No se puede tener por ejemplo, una tabla sin tbody, un div dentro de un p, un enlace dentro de un h1, un enlace dentro de otro, etc.

ngSkipHydration

Otra cosa que debemos evitar es la manipulación directa del DOM, es decir, acceder directamente al document, crear nodos, modificar o borrar elementos, etc. Angular no detectará estos cambios en el proceso de hidratación e interpretará que el DOM generado no coincide. Ejemplo de error: Hydration Node Mismatch.

Si tenemos componentes que manipulan directamente el DOM, por ejemplo de una librería, podemos indicar a Angular que no los tenga en cuenta para la hidratación usando la directiva ngSkipHydration.

Esta directiva solo puede usarse en componentes de Angular, y no en elementos HTML estándar. Los componentes que tengan esta directiva volverán a renderizarse desde cero en el cliente perdiendo los beneficios de la hidratación (solo para estos componentes).

HttpClient con API fetch

Por temas de rendimiento en el servidor, en aplicaciones con Angular SSR activado, se recomienda habilitar que el servicio HttpClient utilice la API Fetch (disponible en NodeJS a partir de la versión 18). Por defecto se utiliza el clásico XMLHttpRequest. Para habilitarlo le pasamos como opción withFetch() a la llamada a provideHttpClient en app.config.ts:

El único inconveniente de usar Fetch es que no se pueden monitorizar los progresos al subir archivos.

<< Integrar librerias Animaciones >>