React
loading.tsx e skeleton senza salti di layout
loading.tsx crea un boundary di Suspense: come scrivere uno skeleton con la stessa forma del contenuto, per non aggiungere CLS.
In sintesi
- `loading.tsx` è un boundary di Suspense creato dalla convenzione di file.
- Lo skeleton deve avere stessa altezza e stesse colonne del contenuto finale.
- Sotto i 200 ms di attesa lo skeleton è peggio del vuoto.
loading.tsx avvolge un segmento in un boundary di Suspense e mostra quel file mentre il server prepara la pagina. Uno skeleton che cambia altezza rispetto al contenuto reale produce CLS, non l’effetto di velocità.
Cosa fa davvero loading.tsx
Il file è zucchero sintattico: Next crea un <Suspense fallback={<Loading />}> attorno al segmento. Non serve import, non serve configurazione, e vale per la pagina e per i segmenti figli che non hanno un proprio loading.tsx.
// app/dashboard/loading.tsx
export default function Loading() {
return (
{Array.from({ length: 6 }).map((_, i) => (
))}
);
}
La regola che evita il salto
Lo skeleton deve occupare lo spazio finale, non uno spazio plausibile. Se la card vera è alta 128 px, lo skeleton è 128 px. Se la griglia ha tre colonne a 768 px, lo skeleton ha le stesse tre colonne allo stesso breakpoint.
| Elemento | Cosa copiare | Cosa evitare |
|---|---|---|
| Altezza | Il valore finale in px o in righe di testo | Un h-24 provvisorio |
| Griglia | Stesse colonne e stesso gap | Un flex diverso dal risultato |
| Testo | Numero di righe plausibile | Parole finte di lunghezza diversa |
| Immagine | Lo aspect-ratio del contenuto | Un quadrato generico |
Quando il salto arriva lo stesso
Due cause ricorrenti: un font che cambia le metriche quando arriva, e un’immagine senza dimensioni. Il primo si risolve con next/font e una metrica di fallback; il secondo con width e height o aspect-ratio. I dettagli sono in layout shift da immagini senza dimensioni e in CLS: cause e correzioni.
Il budget è numerico: CLS sotto 0,1, misurato il 1 novembre 2026 su una pagina del catalogo EneUI che indicizza 111.985 componenti da 1.472 librerie. Lo skeleton è parte del budget, non un dettaglio estetico.
Skeleton presi dal catalogo
Tre record: i componenti skeleton con le forme base, gli stati di caricamento per spinner e progress, e i blocchi dashboard per una griglia che regge il caricamento.
Se il boundary deve gestire un errore e non solo l’attesa, il pattern è in error boundary nell’App Router. Lo streaming che rende utile il fallback è in streaming e Suspense. La struttura delle cartelle è in struttura App Router.
Il limite da mettere in conto
Un loading.tsx al livello del layout radice mostra lo skeleton anche per navigazioni che durano 80 ms: sotto i 200 ms l’attesa non si vede e il lampo è peggio del vuoto.
Domande frequenti
loading.tsx è obbligatorio?
No. Serve quando il segmento ha un’attesa reale. Un progetto senza <code>loading.tsx</code> funziona: l’utente vede la pagina precedente finché la nuova non è pronta.
Come evito il lampo dello skeleton?
Alzando il ritardo di comparsa con una transizione o mettendo il file solo sui segmenti lenti. Sotto i 200 ms l’attesa non si percepisce e il fallback peggiora la sensazione.
Posso animare lo skeleton?
Sì, con <code>animate-pulse</code> o un gradiente. Evita animazioni che spostano il layout: il pulse cambia solo l’opacità, non la dimensione.
Dove metto il loading per una singola sezione?
Con un <code><Suspense></code> esplicito attorno al componente lento. <code>loading.tsx</code> copre l’intero segmento, non una parte.
Perché la pagina salta quando arriva il contenuto?
Perché lo skeleton ha un’altezza diversa, o perché il font e le immagini cambiano le metriche. Confronta il rettangolo finale con quello del fallback, non a occhio.