Para comenzar el post de hoy, daré un breve resumen para que tengamos aún más claro los conceptos
Cuando hablamos de SSR, debemos recordar que está relacionado con el renderizado del lado del servidor, cada vez que se solicite una página se renderizará en el servidor (probablemente haciendo llamadas a la api), y luego se servirá al cliente.
En el caso de SSG es importante que tengamos presente su vinculación con la generación de sitios estáticos, el renderizado de las páginas se hará en tiempo de construcción, y cuando la página sea solicitada se servirá al cliente el archivo estático generado para esa ruta.
Finalmente el CSR guarda relación con el renderizado del lado del cliente, el renderizado de la página (y las llamadas api necesarias para esa página) se realizará en tiempo de ejecución (en el dispositivo del cliente)
¿Qué es la ISR y qué problema resuelve?
Tomemos como ejemplo un sitio de comercio electrónico. Este sitio de comercio electrónico tiene miles de clientes y miles de productos, y para cada producto hay una página de detalles.
Debido a que es un sitio de comercio electrónico, debe ser renderizado del lado del servidor (probablemente usando Angular Universal) para que los rastreadores puedan leer su contenido que es necesario para el SEO.
Ahora, cada vez que se hace una petición directa a esa página de detalles del producto, el servidor tendrá que hacer una llamada api para obtener los datos del backend, luego renderizará el HTML, y luego servirá la página al cliente.
Este proceso ocurre cada vez que un cliente abre esa página.
Ahora imagina que miles de usuarios abren esa página de producto al mismo tiempo. Probablemente el servidor se vea desbordado, y tendríamos que aumentar los recursos del servidor (también los del servidor backend).
El servidor tendrá que hacer el mismo trabajo para todos los clientes, para servirles la misma página.
¿Cómo ha ayudado el SSG hasta ahora?
Con la generación de sitios estáticos, estábamos generando cada página de detalles de productos en tiempo de construcción, haciendo la parte de obtención de datos sólo una vez, y sirviendo archivos estáticos a los usuarios.
Y digamos que esto ayudó mucho con los recursos del servidor en tiempo de ejecución porque sólo serviríamos archivos estáticos y eso es todo.
Esto estaba bien hasta que necesitamos cambiar los detalles del producto, y hacer todo el trabajo desde el principio. Construir el sitio, generar todas las páginas, y desplegar de nuevo.
Todo esto es sólo para cambiar el precio de un producto. Imagina que cambias los precios de 100 productos cada 1 hora. ¿Cuántas veces tendríamos que hacer la construcción, la generación y el despliegue?
Aquí es donde entra en juego la ISR.
- ISR combina la ideología de SSR y SSG en una sola.
- Con ISR, renderizamos la página en el servidor la primera vez que se solicita, la guardamos en la caché y servimos esa página en caché a todos los demás usuarios que la soliciten.
- Para refrescar la caché, utilizamos intervalos de tiempo o regeneración bajo demanda.
- ISR es como SSG, pero en tiempo de ejecución.
¿Todo bien? ¡Hagamos esto en Angular!
- Para empezar, primero necesitamos que una aplicación tenga instalado y configurado Angular Universal.
- Luego, instalamos el paquete ngx-isr.
- ngx-isr te ayuda a gestionar todo el tema de los ISR con una API fácil de usar y extensible (inspirada en Next.js).
npm install ngx-isr
Después de instalarlo, necesitamos hacer algunas pequeñas configuraciones.
Crear una instancia de ISRHandler
dentro de server.ts
.
import { ISRHandler } from 'ngx-isr';
const isr = new ISRHandler({
indexHtml, // <-- Is the path to the index.html
invalidateSecretToken: 'MY_TOKEN', // replace with env secret key
enableLogging: !environment.production
});
Reemplazar el renderizado del lado del servidor por defecto de Angular con el renderizado ISR.
Reemplaza esto:
server.get('*',
(req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
}
);
con este trozo de código :
server.get('*',
// Serve page if it exists in cache
async (req, res, next) => await isr.serveFromCache(req, res, next),
// Server side render the page and add to cache if needed
async (req, res, next) => await isr.render(req, res, next),
);
Nota: ISRHandler proporciona APP_BASE_HREF
por defecto. Y si quieres pasar providers
a los métodos de ISRHandler, también tendrás que proporcionar el token APP_BASE_HREF
Añadir el gestor de URLs de invalidación
server.get(
"/api/invalidate",
async (req, res) => await isr.invalidate(req, res)
);
Añadir NgxIsrModule
en las importaciones de AppServerModule
import { NgxIsrModule } from 'ngx-isr'; // <-- Import module
@NgModule({
imports: [
...
NgxIsrModule // <-- Use it in module imports
]
})
export class AppServerModule {}
Al importar el módulo, NgxIsrService
será inicializado y comenzará a escuchar los cambios de ruta, sólo en el lado del servidor, por lo que el paquete del navegador no contendrá ningún código extra.
¡Eso es todo!
¿Cómo se utiliza?
Añade la llave revalidate
en los datos de la ruta y ya está.
{
path: "example",
component: ExampleComponent,
data: { revalidate: 5 }
}
NOTA: Las rutas que no tienen la llave revalidate
en los datos no serán manejadas por ISR, volverán al pipeline server-side rendering del lado del servidor por defecto de Angular.
Para regenerar una página necesitamos hacer una petición get
a /revalidate
. Así:
GET /api/invalidate?secret=MY_TOKEN&urlToInvalidate=/example
¿Cómo funciona?
Utilizando la clave revalidate
en los datos de la ruta definimos el intervalo de tiempo que el manejador ISR utilizará para saber cuándo regenerar una ruta específica.
Opciones:
- No especificar nada: La ruta no se almacenará en caché y siempre será renderizada por el servidor. (Como SSR)
- 0: El primer servicio será renderizado por el servidor y todos los demás serán servidos desde la caché. (Como SSG).
- Más de 0 (por ejemplo: 5): El primer servicio será renderizado por el servidor y la caché se regenerará cada 5 segundos (después de la última petición).
Ejemplo avanzado
const routes: Routes = [
{
path: "one",
component: PageOneComponent,
},
{
path: "two",
component: PageTwoComponent,
data: { revalidate: 5 },
},
{
path: "three",
component: PageThreeComponent,
data: { revalidate: 0 },
}
];
- Ruta
uno
: No se almacenará en la caché y siempre se renderizará en el servidor antes de ser servido al usuario. - Ruta
dos
: La primera petición será renderizada por el servidor y luego será almacenada en la caché. En la segunda petición, se servirá desde la caché que se guardó en la primera petición. La URL se añadirá a una cola de regeneración, para volver a generar la caché después de5
segundos. En la tercera petición, si la regeneración ha finalizado con éxito, se servirá al usuario la página regenerada; en caso contrario, se le servirá la antigua página en caché. - Ruta
tres
: La primera petición será renderizada por el servidor y luego será almacenada en la caché. Después de la primera petición, todas las demás se servirán desde la caché. Por lo tanto, la caché nunca se refrescará automáticamente. La única forma de refrescar la caché es hacer una petición a /invalidar ruta API.
Resultados
- Servir la página:
npm run dev:ssr
. - Abre el elemento de inspección.
- Comprueba el cambio de fecha y hora de la última actualización en función de la clave de revalidación que ha proporcionado.
¿Problemas con el ISR?
Cada vez que cambiamos el código fuente, tenemos que hacer la construcción y el despliegue de nuevo. El ISR sólo ayuda cuando los datos del backend cambian (y eso está bien).
Eso es todo. ¡Gracias por leer este largo post!