loading.tsx no es un spinner
Un fichero de carga es una frontera de Suspense alrededor de todo un segmento de ruta, así que decide cuánta parte de tu página espera a tu consulta más lenta. Casi todos están puestos por accidente.
Meter un loading.tsx en una ruta te da un estado de carga al instante, y ahí
es donde la mayoría de los equipos deja de leer. El fichero no es un
componente spinner. Es el fallback de una frontera de Suspense que Next.js
pone alrededor del segmento entero, y la frontera es lo que importa.
En la práctica significa: todo lo que hay dentro del segmento espera a lo
más lento que haya dentro. Un loading.tsx arriba de una ruta de panel
convierte una página con cabecera rápida, barra lateral rápida y un gráfico
lento en una página que no muestra nada hasta que el gráfico está listo.
La cabecera y la barra estaban listas en 40 ms. El visitante vio un esqueleto durante dos segundos porque la frontera se dibujó alrededor de las tres cosas.
Dónde va la frontera
Alrededor de lo lento, no alrededor de la página.
// app/dashboard/page.tsx - sin loading.tsx en este directorio.
import { Suspense } from 'react';
export default function Dashboard() {
return (
<>
<Header /> {/* se pinta enseguida */}
<Sidebar /> {/* se pinta enseguida */}
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart /> {/* lo único que espera */}
</Suspense>
</>
);
}El armazón llega por streaming primero, el gráfico llega cuando llega, y mientras tanto el visitante tiene algo que leer y algo que pulsar. El mismo tiempo total, una experiencia completamente distinta, y un LCP mucho mejor, porque el elemento pintado más grande ya no es un esqueleto.
Cuándo sí toca un loading a nivel de ruta
No siempre está mal. Úsalo cuando el segmento entero dependa de verdad de una sola petición: una página de artículo que sin el artículo no es página, una vista de detalle que es un registro. Ahí un fallback a nivel de ruta es honesto: es cierto que todavía no hay nada que enseñar.
La prueba es una pregunta: ¿hay algo en esta página que yo pudiera pintar antes de que lleguen los datos? Si la respuesta es sí, la frontera está en el sitio equivocado.
Dos detalles que cuestan una tarde
Un loading.tsx aplica a su segmento y a todos los segmentos por debajo. Un
fichero en app/loading.tsx es el fallback de toda la aplicación, que casi
nunca es lo que alguien quería decir cuando lo creó trabajando en una ruta.
Y el fallback tiene que reservar el espacio que ocupará su contenido. Un esqueleto de 120 px delante de un gráfico de 400 px es un desplazamiento de 280 px en cuanto llegan los datos, y a CLS le da igual que el salto lo causara tu propio estado de carga.
function ChartSkeleton() {
// La misma altura que el gráfico, para que nada se mueva al cambiar.
return <div className="h-[400px] animate-pulse rounded-xl bg-ink-100" />;
}Eso es todo. loading.tsx es una comodidad para un caso, y <Suspense> es la
herramienta para todos los demás, cosa que conviene saber antes de que la
comodidad decida por ti.
