Saltar al contenido

Prefetch en navegación con preload

Cargar los datos antes de que el usuario llegue a la ruta. Cómo la propiedad `preload` de una definición de ruta dispara las queries en cuanto hay intención de navegar —al pasar el ratón o enfocar un enlace—, cómo el prefetch y el `createAsync` del componente convergen en la misma entrada de caché sin doble petición gracias a `query`, y cómo `preload` corre en paralelo con la carga del código de la ruta para que datos y JavaScript viajen juntos en vez de en serie.

preload en la definición de ruta

Una ruta de SolidStart puede exportar un objeto route con una función preload. El router la ejecuta antes de renderizar el componente, y le pasa los parámetros, la ubicación y la intención de la navegación. Su trabajo es sencillo: llamar a las queries que la ruta va a necesitar.

TSX
// routes/usuarios/[id].tsximport { createAsync, type RouteDefinition } from "@solidjs/router";import { getUsuario } from "~/data/usuarios";export const route = {  preload: ({ params }) => getUsuario(params.id),} satisfies RouteDefinition;export default function UsuarioPage(props) {  const usuario = createAsync(() => getUsuario(props.params.id));  return <h1>{usuario()?.nombre}</h1>;}

Fíjate en un detalle deliberado: preload no espera a la query, solo la invoca. No devuelve el dato al componente ni lo bloquea; se limita a arrancar la carga y calentar la caché. El componente sigue leyendo por su cuenta con createAsync. Prefetch y lectura están desacoplados, y esa separación es la clave del patrón.

El tipo RouteDefinition documenta la forma del objeto y te da autocompletado sobre el argumento que recibe preload. Y conviene saber que preload no corre solo por hover: se ejecuta también en la carga inicial de la ruta —incluido el render en el servidor bajo SSR, donde calienta la caché por petición antes de emitir el HTML— y en cada navegación en firme. El prefetch por intención es solo su modo más vistoso; el mismo gancho sirve a la primera pintada y a la hidratación.

Intención de navegación: hover y foco

Lo que hace mágico a preload no es que corra antes del render, sino cuándo el router decide llamarlo. Por defecto, el componente A del router dispara el preload de su ruta destino en cuanto hay intención de ir: al pasar el ratón por encima del enlace o al enfocarlo con el teclado. Entre ese gesto y el clic pasan decenas o cientos de milisegundos, y en esa ventana la red ya está trabajando.

TSX
import { A } from "@solidjs/router";// Al pasar el raton o enfocar, el router llama al preload de /usuarios/7<A href="/usuarios/7">Ver perfil</A>

El preload recibe la intención en su argumento para que puedas afinar la política: la intención preload es el prefetch especulativo por hover o foco; navigate es el preload en firme al confirmar la navegación; initial es la primera carga de la app. Puedes decidir prefetchar de forma agresiva solo en algunas y ser conservador en otras.

El comportamiento es configurable en dos niveles. En el Router decides la política global; en cada enlace A puedes forzarla o desactivarla con su prop preload, útil para enlaces que apuntan a pantallas caras que rara vez se visitan.

TSX
// Global: activa el prefetch por intencion en toda la app<Router preload={true}>{/* rutas */}</Router>// Local: este enlace NO prefetcha, aunque el router lo haga por defecto<A href="/informe-pesado" preload={false}>Informe</A>

preload + query = sin doble petición

Aquí encaja lo del nivel anterior. El preload llama a getUsuario(params.id) y el componente hace createAsync(() => getUsuario(props.params.id)): la misma query con el mismo argumento, luego la misma clave. La deduplicación de query hace el resto. El preload arranca la petición y la cachea; cuando el componente monta y su createAsync pide la misma clave, no dispara una segunda red: recoge la entrada que el preload dejó lista.

Sin query, el prefetch sería contraproducente: pedirías el dato al hacer hover y otra vez al montar, duplicando la carga. Es la memoización por clave la que convierte dos invocaciones separadas en el tiempo en una sola petición. Por eso preload no devuelve datos: no los necesita mover, porque la caché compartida ya es el canal por el que viajan del prefetch al componente.

Una ruta suele necesitar varias piezas de datos, y preload es el sitio para calentarlas todas de golpe. Como ninguna se espera, salen en paralelo, y cada componente anidado leerá la suya por su clave sin coordinarse con los demás.

TSX
export const route = {  preload: ({ params }) => {    getUsuario(params.id);   // cabecera    getPosts(params.id);     // cuerpo    getSeguidores(params.id); // barra lateral  },} satisfies RouteDefinition;

Datos y código en paralelo

El segundo beneficio es más sutil y suele importar más. En una SPA clásica, navegar a una ruta encadena dos esperas: primero descarga el chunk de JavaScript de esa pantalla, y solo cuando el código corre empieza a pedir sus datos. Es una escalera: código, luego datos. preload la aplana. El router lanza el preload de una ruta a la vez que descarga su código, de modo que la red de datos y la de JavaScript avanzan en paralelo. Cuando el componente termina de cargar y monta, sus datos suelen estar ya listos, porque llevaban descargándose todo ese rato.

Esta paralelización enlaza directamente con la última lección del nivel. El waterfall más común de todos es precisamente ese —código y luego datos, en serie— y el preload lo elimina de raíz sin que tengas que pensarlo, con solo mover la invocación de las queries del cuerpo del componente a la definición de la ruta. Prefetch por intención y ausencia de waterfall inicial son, vistos de cerca, la misma jugada: declarar arriba lo que la pantalla necesitará, para que viaje junto en vez de en fila.