Instant Navigation con Speculation Rules API
Tags: webdev, webperf, javascript, frontend
Published: 2024-06-26 05:54:45

Uno de las fantasías de todo frontend es que la navegación entre las diferentes partes de nuestra web sea instantanea como si de alguna manera la web estuviera lista para la página a la que queremos acceder.

El browser nos ofrece mecanismos como los [resource hints](https://web.dev/learn/performance/resource-hints) para poder hacer precargas (entre los mas conocidos preload y prefetch) de recursos lo cual nos da la posibilidad de reducir tiempos de carga.

Antes de tenía un resource hint llamado prerender pero fue eliminado por múltiples bugs pero ahora regreso en forma de ficha con la [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API).

## Speculation Rules API

Antes de empezar, una aclaración preload y prefetch sirven para hacer el request de recursos para que cuando se usen ya se hayan descargado (Hay mas diferencias pero quiero simplificar). Esto en archivos de assets esta perfecto pero cuando se solicitan documentos HTML estos solo se renderizarán cuando ese documento se cargue en el contexto del browser.

Si quisieramos cargar algún recurso preload y prefetch son suficientes, esto normalmente tiene mayor uso en una Single Page App (SPA), en una SPA la navegación es mas compleja porque todo se administra dentro del contexto de la aplicación. Cuando tenemos una Multi Page App (MPA) donde la navegación es simple pero se cambia todo el contexto de la aplicación. Aquí es donde Speculation Rules API nos permite especificar que URLs podemos [prerenderizar](https://developer.chrome.com/docs/web-platform/prerender-pages) para darnos una sensación de inmediates.

### Advertencia

La implementación de Speculation Rules API también tiene consecuencias, al hacer un prerender nuestro JS se ejecuta. Si tenemos analytics podemos estar introduciendo ruido a nuestras métricas ya que estamos "Especulando" ya que nada nos garantiza que el usuario va a acceder a las urls que especificamos.

> Adicionalmente sería bueno considerar el tipo de dispositivo y conexión

### Demo

![Sitio web con links a page2 y page3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqrejva0dthy6qlyeruu.png)

Cree un sitio sencillo con 3 páginas: index, page2 y page3 (que creativo).

El servidor que cree para servir el contenido hace que cualquier URL que termine en .html tendrá un delay de 2 segundos (Esta parte es opcional y se hizo por motivos dramáticos XD)

```javascript
const express = require("express");
const path = require("path");

const app = express();
const port = process.env.PORT || 9000;

// Define the static files directory
const staticPath = path.join(__dirname, "public");

// Middleware to delay response for HTML files
app.use((req, res, next) => {
  if (req.url.endsWith(".html")) {
    setTimeout(() => next(), 2000); // Delay by 2 seconds
  } else {
    next();
  }
});

// Serve static files
app.use(express.static(staticPath));

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});
```

Código Page2

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Page 2</title>
  </head>
  <body>
    <h1>Page 2</h1>
  </body>
</html>
```

Código Page3
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Page 3</title>
  </head>
  <body>
    <h1>Page 3</h1>
  </body>
</html>
```

Código index.html
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Main</title>
    <script type="speculationrules">
      {
        "prerender": [
          {
            "urls": ["page2.html"]
          }
        ]
      }
    </script>
  </head>
  <body>
    <h1>Speculation API</h1>
    <h2><a href="page2.html">Page 2</a></h2>
    <h2><a href="page3.html">Page 3</a></h2>
  </body>
</html>

```

> La Speculation Rules API puede ser usada como un tipo de script o también como un JSON externo pero este debe ser especificado en un Header.

Speculation Rules API no es un API que pueda usarse directamente con JS, funciona como un JSON donde especificas una configuración.

Si usas Chrome, antes de ejecutar el demo te sugiero abrir las developer tools, ve a Application y busca en el panel izquierdo una sección llamada "Speculative loads". Ahí podras ver que la página se cargó (aunque igual podrías usar Network).

![Speculative loads](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbtjg8o5nn1ow8nvae52.png)

Da click en Page 3 y después a Page 2. ¿Ves la diferencia? Ahora si, vamos a explicar al responsable de lo que acaba de suceder.

```html
<script type="speculationrules">
      {
        "prerender": [
          {
            "urls": ["page2.html"]
          }
        ]
      }
    </script>
```

En la [referencia de Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) nos especifíca que la configuración es un objeto con 2 propiedades `prerender` y `prefetch`. Por ahora nos enfocaremos en `prerender`.

De acuerdo a la [documentación](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/speculationrules) las reglas que usamos tienen propiedades como urls, un arreglo donde ponemos las URLs que queremos prerenderizar.

> También existe la propiedad where para hacer una selección mas avanzada de aquello que queremos prerenderizar (lo abordaremos en otros posts)

Hay una propiedad llamada [eagerness](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/speculationrules#eagerness) que le indica al browser "cuando" se debería hacer el prefetch/prerender de los recursos especificados en la regla.

Por defecto para lo especificado en "urls", el eagerness tiene un valor de "immediate". Lo que quiere decir que el prerendering comenzará tan pronto como sea posible. Prueba agregando esa propiedad con el valor de "moderate" (no olvides abrir las devtools para ver que sucede)

```html
<script type="speculationrules">
      {
        "prerender": [
          {
            "urls": ["page2.html"],
            "eagerness": "moderate"
          }
        ]
      }
    </script>
```

Te dirá que hay una regla que no se ha disparado

![Devtools speculation rules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mk2m9fu2veiw1hq7eev0.png)

Ahora pon el cursor sobre el link y veras como se dispara el evento y hace el prerender. Esto es muy util para evitar prerenders innecesarios.

En futuros post abordaremos mas sobre los diferentes tipos de eagerness. Nota: tu no tienes forma de saber cuando ocurriran estos eventos de manera exacta, eso lo decide el browser.

## Consideraciones

Tu script de analytics y ejecución de ciertos scripts que se deben ejecutar únicamente cuando el usuario visite el documento tendrá que tener código adicional.

Mira el siguiente código rob...inspirado en la documentación de Mozilla

```javascript
if (document.prerendering) {
  document.addEventListener("prerenderingchange", initAnalytics, {
    once: true,
  });
} else {
  initAnalytics();
}
```

Si bien el API no puede ser usada directamente con JS, puedes usar JS para agregarlas dinámicamente

```javascript
if (
  HTMLScriptElement.supports &&
  HTMLScriptElement.supports("speculationrules")
) {
  const specScript = document.createElement("script");
  specScript.type = "speculationrules";
  const specRules = {
    prefetch: [
      {
        source: "list",
        urls: ["/next.html"],
      },
    ],
  };
  specScript.textContent = JSON.stringify(specRules);
  document.body.append(specScript);
}
```

Habrá mas post, sobre esta API en el futuro, integraciones con react y como usarla en producción sin preocupaciones aunque esta API sea experimental.

Si les gusta compartan, sino pues también XD. Nos vemos en futuros posts