// page.tsx - Evităm blocarea întregii pagini prin Suspense granular
import { Suspense } from 'react';
import { UserProfile, Transactions, Notifications } from './components';
import { ProfileSkeleton, TxSkeleton, NotifSkeleton } from './skeletons';
export default async function DashboardPage() {
return (
<div className="grid grid-cols-3 gap-4">
{/* Profilul se încarcă instant (150ms) */}
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile />
</Suspense>
{/* Tranzacțiile se încarcă mediu (600ms) */}
<Suspense fallback={<TxSkeleton />}>
<Transactions />
</Suspense>
{/* Notificările grele nu mai blochează restul paginii (2.4s) */}
<Suspense fallback={<NotifSkeleton />}>
<Notifications />
</Suspense>
</div>
);
}Salutare. Am văzut multă confuzie pe forum legată de cum se împacă loading.tsx din Next.js cu <Suspense>-ul nativ din React, mai ales de când cu streaming SSR. Am avut recent de optimizat un dashboard administrativ cu vreo 15 widget-uri grele și am testat ambele abordări la sânge ca să văd unde crapă UX-ul.
Diferența conceptuală și unde se încurcă lumea
La prima vedere, par exact același lucru. Scrii loading.tsx în folderul rutei și Next.js îți pune automat un Suspense boundary peste pagină. E simplu, dar e o capcană de performanță.
loading.tsx acționează la nivel de rută (layout-level). În spate, Next.js pune tot ce e în page.tsx într-un singur <Suspense fallback={<Loading />}>.
Trade-off-ul? Dacă ai trei componente server-side în acea pagină (să zicem: profilul utilizatorului, lista de tranzacții și un feed de notificări), utilizatorul nu va vedea absolut nimic din pagină până nu se încarcă cel mai lent dintre query-uri. Practic, anulezi beneficiul streaming-ului pe bucăți.
Am pățit asta la un proiect cu 8k utilizatori activi: widget-ul de notificări (care făcea un query greoi pe o bază de date legacy) ținea blocat tot ecranul timp de 2.4 secunde, deși datele de profil erau gata în doar 150ms. Utilizatorul vedea un spinner mare pe tot ecranul în loc să poată citi deja informațiile de profil.
Când coborâm în tranșee cu Suspense granular
Aici intervine <Suspense>-ul folosit manual în interiorul paginii. În loc să lași Next.js să decidă la nivel de rută, spargi pagina în componente asincrone mai mici și le înfășori individual.
Streaming-ul prin HTTP (Streaming SSR) strălucește cu adevărat aici. Serverul trimite scheletul static al paginii instant (header, sidebar, layout). Pe măsură ce query-urile se rezolvă pe server, bucăți de HTML (chunks) sunt "pompate" prin conexiunea HTTP deschisă și înlocuiesc fallback-urile de Suspense direct în browser, fără ca utilizatorul să simtă vreun delay de navigare.
Dar există un trade-off major și aici: Layout Shift (CLS). Dacă nu definești înălțimi minime fixe pentru fallback-urile tale (scheletele de loading), pagina o să se tot miște în sus și în jos pe măsură ce widget-urile se încarcă pe rând, ceea ce enervează la culme utilizatorul.
Regula mea de aur pentru producție
După câteva refactorizări dureroase, am rămas cu o rețetă simplă pe care o aplic acum la orice proiect:
- Folosesc
loading.tsxexclusiv pentru pagini simple sau ca o plasă de siguranță globală (de exemplu, când treci de la Dashboard la Setări și vrei să indici vizual că se întâmplă o tranziție de pagină). - Folosesc
<Suspense>granular direct în pagină pentru orice componentă care depinde de un API extern, un query SQL greu sau un serviciu terț.