# loading.tsx and skeletons without layout jumps

loading.tsx creates a Suspense boundary: how to write a skeleton that matches the final shape of the real content, so it adds no CLS.

> Page: https://ui.eneawork.it/blog/nextjs-loading-skeleton
> Markdown: https://ui.eneawork.it/blog/nextjs-loading-skeleton.md

*2026-11-01*

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.

## 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.

Skeleton identico alla forma finale, oppure nessuno skeleton. La via di mezzo è il salto di layout.

## FAQ

### loading.tsx è obbligatorio?

No. Serve quando il segmento ha un’attesa reale. Un progetto senza loading.tsx 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 animate-pulse 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 <Suspense> esplicito attorno al componente lento. loading.tsx 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.
