Coordinar varios recursos y evitar la cascada de spinners
Un solo Suspense puede esperar a muchos recursos a la vez: su contador suma todas las lecturas pendientes y revela todo junto, evitando el goteo de indicadores sueltos y el salto de layout. El enemigo real no es el límite sino la topología de dependencias: si el origen de un recurso lee el resultado de otro, sus fetch se serializan en cascada y el tiempo total es la suma en vez del máximo. La cura es separar orígenes para que carguen en paralelo, leer el grafo de orígenes de una pantalla con varios recursos, y anidar Suspense cuando prefieras revelar por regiones independientes en lugar de todo a la vez.
Un solo límite, un revelado conjunto
El contador de un Suspense suma todas las lecturas pendientes de su subárbol. Si tres recursos se leen bajo el mismo límite, el fallback permanece hasta que los tres resuelven, y entonces los hijos aparecen de golpe. Esto es exactamente lo que quieres para una sección cohesiva —una cabecera, su avatar y sus estadísticas—: nada de que el nombre aparezca, luego la foto salte y después el contador empuje el layout. Un límite, un momento de revelado.
import { createResource, Suspense } from "solid-js";function Panel(props: { id: string }) { const [perfil] = createResource(() => props.id, cargarPerfil); const [stats] = createResource(() => props.id, cargarStats); return ( <Suspense fallback={<EsqueletoPanel />}> <Cabecera datos={perfil()} /> <Metricas datos={stats()} /> </Suspense> );}Ambos recursos toman props.id como origen de forma independiente: ninguno lee el resultado del otro. Por eso sus fetch arrancan a la vez y el fallback dura lo que tarde el más lento, no la suma de los dos. Ese detalle —orígenes independientes— es la frontera entre un panel que carga en paralelo y uno que se arrastra en cascada.
La cascada: cuando un recurso espera a otro
La cascada aparece en cuanto el origen de un recurso lee el valor de otro. El origen de un createResource se re-evalúa reactivamente; si dentro llamas al accessor de otro recurso, el segundo no puede ni empezar hasta que el primero resuelva, porque su origen es undefined mientras tanto. El resultado es una fila de esperas encadenadas.
// CASCADA: posts no arranca hasta que usuario resuelve, porque su origen lo lee.const [usuario] = createResource(() => props.id, cargarUsuario);const [posts] = createResource(() => usuario()?.equipoId, cargarPosts);// PARALELO: ambos parten de props.id y cargan a la vez.const [usuario2] = createResource(() => props.id, cargarUsuario);const [posts2] = createResource(() => props.id, cargarPostsDeUsuario);A veces la cascada es inevitable y correcta: si de verdad necesitas el equipoId que solo trae el usuario para pedir los posts, no hay atajo. Pero muchas cascadas son accidentales —pediste juntos datos que podían pedirse por separado— y cuestan latencia gratis. La regla es afirmativa: haz que cada recurso arranque desde el dato que ya tienes, no desde el que otro recurso aún está trayendo.
Tres recursos: el grafo de orígenes
Con dos recursos el análisis es fácil; con tres o cuatro conviene dibujar mentalmente el grafo de orígenes —qué lee cada fetch— antes de escribirlo. Imagina una vista de proyecto: el proyecto por su id, sus miembros por ese mismo id, y los comentarios que sí requieren el id del hilo que solo trae el proyecto. Dos ramas paralelas y una dependencia real.
function Proyecto(props: { id: string }) { const [proyecto] = createResource(() => props.id, cargarProyecto); const [miembros] = createResource(() => props.id, cargarMiembros); // paralelo const [comentarios] = createResource( () => proyecto()?.hiloId, // dependencia real cargarComentarios, ); return ( <Suspense fallback={<EsqueletoProyecto />}> <Ficha proyecto={proyecto()} miembros={miembros()} /> <Hilo comentarios={comentarios()} /> </Suspense> );}| Recurso | Origen | Arranca |
|---|---|---|
proyecto |
props.id |
de inmediato |
miembros |
props.id |
de inmediato, en paralelo con proyecto |
comentarios |
proyecto()?.hiloId |
solo tras resolver proyecto |
Aquí proyecto y miembros arrancan juntos; comentarios espera solo a proyecto, no a miembros. El tiempo total no es la suma de los tres, sino el del camino más largo del grafo: proyecto seguido de comentarios. Leer así los orígenes te dice de un vistazo dónde está la ruta crítica y qué dependencias podrías romper para acortarla —quizá el servidor ya te da el hiloId en la misma respuesta del proyecto, y la cascada desaparece—.
Anidar Suspense para revelar por regiones
Un único límite tiene una contrapartida: todo espera a lo más lento. Si una parte de la página tiene datos rápidos y otra depende de un servicio pesado, coordinarlos en el mismo Suspense toma como rehén al contenido veloz. La solución es anidar límites: un Suspense externo para el armazón que llega pronto y límites internos para las regiones lentas, cada uno con su propio fallback.
<Suspense fallback={<EsqueletoPagina />}> <Cabecera datos={perfil()} /> <Suspense fallback={<EsqueletoFeed />}> <Feed datos={feedLento()} /> </Suspense></Suspense>;Ahora la cabecera se revela en cuanto su recurso está listo, mientras el feed sigue mostrando su propio esqueleto. Un límite interno que suspende no re-suspende al externo: una vez revelado, un Suspense padre no vuelve a ocultarse porque un hijo suyo entre en carga. Eliges así la granularidad del revelado —todo junto con un límite, por regiones con varios— según qué experiencia quieras. La coordinación es una herramienta de diseño, no un ajuste técnico.