Saltar al contenido

preload en rutas: cargar antes de renderizar

Una ruta puede exportar `preload` para calentar la caché en cuanto el usuario navega o incluso hace hover sobre un enlace, antes de que el componente se monte. Cómo se declara el objeto `route` con `satisfies RouteDefinition`, por qué `preload` dispara las queries sin esperarlas, cómo el componente Anchor precarga en hover y focus con `preloadDelay`, y qué significa cada valor de `intent` (initial, native, navigate, preload) para razonar cuándo y por qué corre la precarga.

El waterfall de renderizar y luego pedir

Sin precarga, la secuencia es una cascada: el router monta el componente, el componente ejecuta createAsync, y solo entonces sale la petición. El tiempo de red se suma después del tiempo de navegación, y si el componente tiene hijos que también piden, la cascada se alarga escalón a escalón. El usuario paga esa suma como latencia visible.

TSX
// Sin preload: la peticion no arranca hasta que el componente existefunction Detalle(props: { id: string }) {  const producto = createAsync(() => getProducto(props.id)); // pide AL montar  return <Suspense fallback={<Esqueleto />}>{/* ... */}</Suspense>;}

La idea de preload es adelantar ese disparo: mover el momento en que la petición empieza desde “cuando el componente se monta” hasta “cuando el router sabe que vas hacia esa ruta”, que puede ser bastante antes.

preload: calentar la caché antes de renderizar

Una ruta exporta un objeto route con una función preload. El router la invoca al entrar en la ruta, y dentro llamas a las mismas queries que el componente usará. La clave del patrón: no esperas la promesa —la disparas y sigues—. Con eso la query queda en vuelo y su valor se deposita en la caché bajo su clave; el componente, al montarse, lo relee y lo encuentra ya resuelto o casi.

TSX
// ~/routes/productos/[id].tsximport type { RouteDefinition } from "@solidjs/router";import { createAsync, useParams } from "@solidjs/router";import { getProducto } from "~/lib/productos";export const route = {  preload({ params }) {    void getProducto(params.id); // dispara la query, NO la espera  },} satisfies RouteDefinition;export default function Detalle() {  const params = useParams();  const producto = createAsync(() => getProducto(params.id)); // relee la cache caliente  return (    <Suspense fallback={<Esqueleto />}>      <h1>{producto()?.nombre}</h1>    </Suspense>  );}

El void es deliberado: comunica que ignoras el valor a propósito. preload no está para devolver datos —ese es el trabajo de createAsync—, sino para arrancar su carga. La única fuente de verdad sigue siendo la query cacheada; preload solo adelanta el instante en que se llena. Como createAsync y preload llaman a la misma query con la misma clave, la deduplicación garantiza que no haya doble petición: si la precarga sigue en vuelo cuando el componente lee, ambos comparten la misma promesa.

Precargar en hover con el componente A

Aquí preload despliega su verdadera potencia. El componente de enlace A del router no espera a que hagas clic: al pasar el ratón por encima o al enfocar el enlace con el teclado, ejecuta el preload de la ruta destino. Cuando el clic llega, los datos suelen estar ya en caché y la navegación se siente instantánea.

TSX
import { A } from "@solidjs/router";// Al hacer hover o focus sobre el enlace, se dispara el preload de /productos/7<A href={`/productos/${p.id}`}>{p.nombre}</A>

El comportamiento se gobierna de forma global y por enlace. En el Router fijas el retardo con preloadDelay —los milisegundos de hover antes de precargar, para no disparar en cada barrido del ratón— y puedes desactivarlo del todo; en un enlace concreto lo anulas con preload={false} cuando la ruta destino es cara y no quieres precargar de más.

TSX
import { Router } from "@solidjs/router";<Router preload={true} preloadDelay={200}>  {/* rutas */}</Router><A href="/informe-pesado" preload={false}>Informe</A>

intent: por qué se dispara la precarga

preload recibe un objeto con params, location e intent, y este último te dice por qué corre en cada invocación. Distinguirlo te permite escribir precargas más finas —por ejemplo, hacer un trabajo caro solo en la navegación real y no en cada hover—.

TSX
export const route = {  preload({ params, location, intent }) {    void getProducto(params.id);    if (intent === "navigate") void getResenas(params.id); // extra solo al navegar de verdad  },} satisfies RouteDefinition;

Los cuatro valores cubren cada forma de entrar en una ruta. initial es la carga inicial de la página, típicamente durante el SSR. native es una navegación del navegador —atrás, adelante, recargar—. navigate es una navegación del router provocada por el usuario dentro de la app. Y preload es la precarga especulativa que dispara el hover o el focus sobre un enlace A, antes de que haya ningún clic. Leer intent es leer la intención del router: la misma función se ejecuta en los cuatro casos, pero tú decides cuánto trabajo hacer en cada uno.

Un matiz que evita bugs sutiles: preload corre tanto en el servidor —en la carga inicial— como en el cliente —en cada navegación y hover posterior—. Por eso su cuerpo debe ser seguro en ambos entornos. Limítalo a disparar queries, que ya saben cruzar la frontera red y serializarse; no metas ahí acceso directo al DOM ni APIs solo de navegador, porque en el SSR no existen y la precarga fallaría en silencio justo donde más la necesitas. Como heurística: si una línea de preload no sería válida en un servidor sin ventana, no pertenece a preload.