client:load y client:idle: urgencia y paciencia
Las dos directivas del extremo impaciente del espectro: client:load hidrata en cuanto el navegador puede, para lo que debe responder desde el primer instante; client:idle espera a que el hilo principal quede libre con requestIdleCallback, con un timeout opcional que garantiza un tope. Elegir entre ellas es una decisión de planificación del hilo principal, no de velocidad.
De las cinco directivas client:*, dos hidratan sin esperar a nada externo —ni al scroll, ni a una media query—: client:load y client:idle. Ambas viven en el extremo impaciente del espectro, pero se diferencian en algo decisivo: la urgencia. Una hidrata en cuanto el navegador puede; la otra espera cortésmente a que el hilo principal tenga un respiro. Elegir entre ellas es repartir tiempo de CPU en los primeros segundos de la página, donde ese tiempo es más escaso.
- Usar
client:loadpara interactividad crítica desde el primer instante. - Usar
client:idlepara interactividad real pero no urgente. - Entender
requestIdleCallbacky eltimeoutopcional declient:idle. - Decidir entre ambas según la presión sobre el hilo principal.
client:load: hidrata ya
client:load es la directiva de máxima prioridad. Marca la isla para que se hidrate en cuanto su JavaScript esté disponible, sin esperar a ninguna condición. El runtime importa el framework y el componente de inmediato y ejecuta la hidratación tan pronto como puede, compitiendo por el hilo principal con todo lo demás que arranca al cargar la página.
---
import BotonComprar from '../components/BotonComprar.jsx';
---
<BotonComprar client:load />
Esa urgencia es exactamente lo que quieres para lo que debe funcionar desde el primer segundo y está a la vista al entrar. Son buenos candidatos:
- Un botón de compra o de acción principal sobre el pliegue.
- Un menú principal que se despliega al tocarlo.
- Un campo de búsqueda con foco automático.
- Un conmutador de tema que evita el parpadeo de color al cargar.
Para todo eso, cualquier demora se percibe como un fallo: el usuario hace clic y no pasa nada. client:load compra respuesta inmediata al precio de gastar hilo principal justo cuando más escaso es. Y ese precio es real: cada isla client:load descarga, analiza y ejecuta su código durante el arranque, el momento en que el navegador ya está ocupado pintando y preparando la página. Un puñado de islas client:load pesadas dispara el TBT y retrasa el momento en que la página responde.
Al venir de una SPA, la tentación es marcar todo con client:load para que “todo funcione ya”. Es el antipatrón más común en Astro: reconstruye, isla a isla, el mismo hilo principal saturado del que huías. Antes de escribir client:load, pregúntate si la isla se ve al entrar y si su demora sería perceptible. Si la respuesta a cualquiera de las dos es no, otra directiva servirá mejor.
client:idle: hidrata en el respiro
client:idle conserva la garantía de que la isla se hidratará —no depende de que el usuario haga scroll ni de una media query—, pero cede la prioridad: espera a que el hilo principal quede libre. Por debajo usa requestIdleCallback, la API del navegador pensada justo para esto, que ejecuta trabajo de baja prioridad en los huecos entre tareas urgentes. En navegadores sin esa API, Astro recurre a un setTimeout como red de seguridad.
---
import Recomendaciones from '../components/Recomendaciones.jsx';
---
<Recomendaciones client:idle />
Conviene entender qué aplaza exactamente. A diferencia de client:load, que importa el módulo cuanto antes, client:idle programa esa importación y la hidratación para el primer momento ocioso: difiere el trabajo entero, no solo el último paso. El resultado es interactividad que llega un instante después de lo crítico, sin robarle CPU al arranque. Encaja de maravilla en cosas como:
- Un panel de recomendaciones o de contenido relacionado.
- Un widget de compartir o de reacciones.
- Un selector de idioma o de moneda en la cabecera.
- Adornos y analíticas que deben cargar, pero sin prisa.
Hay un matiz de control fino. client:idle acepta un timeout opcional, client:idle={{timeout: 500}}, que se pasa a requestIdleCallback como tope: si en ese plazo el hilo nunca llega a estar ocioso, el navegador fuerza la hidratación de todos modos. Sirve para poner un límite superior a la espera y evitar que una isla se quede sin hidratar indefinidamente en una página que nunca descansa.
---
import Recomendaciones from '../components/Recomendaciones.jsx';
---
<Recomendaciones client:idle={{timeout: 500}} />
client:idle no descarga menos JavaScript que client:load: descarga lo mismo, solo que más tarde. Si tu problema es el peso total de la isla, la directiva no lo resuelve; para eso están aligerar el componente o no crearlo. Lo que idle resuelve es el momento del gasto: lo aparta del arranque, cuando el hilo está saturado, y lo coloca en el primer hueco. Es una palanca de planificación, no de dieta.
Repartir el hilo principal
Pensar en client:load frente a client:idle como “rápido” frente a “lento” despista. Las dos hidratan pronto; la diferencia es quién va primero cuando varias islas compiten por el mismo hilo. client:load dice “yo antes que la pintura y el resto”; client:idle dice “cuando todos hayan pasado, en el primer hueco”. Es planificación, no velocidad. En una misma página conviven sin coordinarse, cada una con su política declarada al lado:
---
import Tema from '../components/Tema.jsx';
import Menu from '../components/Menu.jsx';
import Recomendaciones from '../components/Recomendaciones.jsx';
---
<Tema client:load /> <!-- critico: evita el parpadeo de color -->
<Menu client:load /> <!-- visible y usable al entrar -->
<Recomendaciones client:idle /> <!-- util, pero puede esperar al respiro -->
client:load
Hidrata en cuanto llega el JS, con máxima prioridad. Para lo crítico y visible al entrar.
client:idle
Espera al primer hueco del hilo con requestIdleCallback. Para lo real pero no urgente.
timeout
client:idle={{timeout: 500}} pone un tope a la espera si el hilo nunca descansa.
El criterio
¿Se ve al entrar y su demora se notaría? Si no a alguna, idle antes que load.
flowchart LR P[pagina llega al navegador] --> L[client load hidrata de inmediato] P --> BUSY[hilo principal ocupado con pintura y arranque] BUSY --> GAP[aparece un hueco libre] GAP --> I[client idle hidrata en el hueco] L --> COST[coste pagado durante el arranque] I --> AFTER[coste diferido al respiro] style L fill:#f38ba8,color:#11111b style I fill:#a6e3a1,color:#11111b style COST fill:#f38ba8,color:#11111b style AFTER fill:#a6e3a1,color:#11111b
No puedes cambiar la directiva de una isla desde el cliente: es una decisión del autor, fijada en el código y por cada uso del componente. El mismo componente puede ser client:load en una página y client:visible en otra, según su papel allí. La política de hidratación queda así explícita y local, legible de un vistazo junto al componente, en lugar de escondida en una configuración global que haya que ir a buscar.
Dónde caen en el espectro
Estas dos directivas son solo el extremo impaciente de un abanico de cinco, que conviene tener presente entero desde ya:
client:load— la más impaciente: en cuanto llega el código.client:idle— en el primer hueco del hilo principal.client:visible— cuando la isla entra en pantalla.client:media— cuando una media query se cumple.client:only— sin servidor, renderizada entera en el cliente.
Una heurística que envejece bien: reparte tus islas impacientes en dos grupos. Las que el usuario podría tocar en el primer segundo —y que están a la vista— van con client:load. Las demás que igual deben acabar hidratadas sin depender del scroll van con client:idle. Ese simple corte suele bastar para bajar el TBT sin que nadie perciba una pérdida de interactividad, porque lo que se difiere es justo lo que nadie iba a usar en ese primer instante.
Es tentador leer las directivas como un dial de velocidad —load rápido, idle más lento— pero esa lectura oculta lo que de verdad manejas. El hilo principal del navegador es un recurso único y secuencial: solo puede hacer una cosa a la vez, y durante el arranque de una página tiene una cola densa de trabajo obligatorio —parsear HTML, construir el DOM, aplicar estilos, pintar—. Cada isla que hidratas es trabajo que insertas en esa cola, y la directiva es el mecanismo con el que decides en qué posición lo insertas. client:load lo mete al principio, delante de casi todo; client:idle lo mete al final, en el primer resquicio que quede. No estás eligiendo cómo de rápido corre una isla, sino a quién le quitas turno. Verlo así cambia la pregunta que te haces. Deja de ser “¿quiero esto rápido?” —a lo que siempre contestarías que sí— y pasa a ser “¿esto merece ir por delante de que la página se pinte y responda?” —a lo que casi siempre contestarás que no—. La maestría en el rendimiento de Astro no está en hidratar rápido, sino en ordenar con avaricia: conceder la prioridad máxima solo a lo que el usuario tocaría en el primer segundo, y empujar todo lo demás al respiro. Un hilo principal es un presupuesto de tiempo, y cada client:load es un cargo con fecha de hoy; client:idle es el mismo cargo aplazado a cuando sobre. Administrar bien esa cola es administrar la sensación de que la página va rápida.
- Monta una página con tres islas visibles al entrar: un menú principal, un selector de moneda y un widget de “me gusta”.
- Marca las tres con
client:loady mide el TBT en las herramientas del navegador. - Deja
client:loadsolo en el menú, pasa las otras dos aclient:idley observa cómo baja el TBT. - Añade un
timeoutalclient:idley razona en qué tipo de página —una que nunca descansa— ese tope marca la diferencia.