eduardweb.
Hooks & PatternsAvansat#nextjs#react#ssr#web-performance

React Suspense granular vs loading.tsx în Next.js: când folosești fiecare

De Maria Vasilescu, 14 oct. 2026 · 6 vizualizări · 3 like-uri

Postat acum 2 zile
typescript
import { Suspense } from 'react';
import { ProductDetails } from './ProductDetails';
import { StockStatus } from './StockStatus';
import { ReviewsSkeleton } from './skeletons';
import { ReviewsList } from './ReviewsList';

export default async function ProductPage({ params }: { params: { id: string } }) {
  return (
    <main className="max-w-4xl mx-auto p-4">
      {/* Shell-ul principal se randează instant la SSR */}
      <ProductDetails id={params.id} />
      <StockStatus id={params.id} />

      {/* Datele lente vin prin streaming fără să blocheze FCP */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ReviewsList productId={params.id} />
      </Suspense>
    </main>
  );
}

Am văzut zeci de proiecte unde echipa trântește un loading.tsx în rădăcina unui route segment și consideră treaba terminată. După care clienții se plâng că ecranul clipește violent la fiecare navigare, deși avem „performanță pe server”. Hai să vedem cum funcționează streaming SSR sub capotă și unde își are locul fiecare mecanism.

Ce este de fapt loading.tsx?

Fișierul loading.tsx din Next.js App Router este doar o convenție automată de nesting. Când îl adaugi într-un folder, framework-ul generează în spate un wrapper de genul:

<Suspense fallback={<Loading />}>{children}</Suspense>

Adică împachetează absolut tot ce se află în page.tsx și rutele copil. Dacă ai o pagină complexă — să zicem un dashboard de analytics — și un singur query dintr-un grafic durează 1.4 secunde, utilizatorul vede skeleton-ul paginii întregi timp de 1.4 secunde. Shell-ul, header-ul local, filtrele statice și tab-urile dispar complet din UI în timpul tranziției dacă navigarea nu e optimizată.

E o abordare utilă când schimbi contextul radical (treci de la /billing la /analytics), dar extrem de agresivă pentru pagini cu date eterogene.

Granularitate cu Suspense și streaming SSR

La un magazin online pe care l-am migrat recent, cu vreo 25k cereri pe zi pe pagina de produs, aveam o problemă clasică: detaliile de bază (titlu, poze, specificații) veneau din cache în 35ms. Recenziile și stocul în timp real din ERP durau între 600ms și 1.2 secunde.

Cu loading.tsx, FCP-ul (First Contentful Paint) era tras în jos inutil. Utilizatorul stătea în fața unui schelet albicios mai mult de o secundă.

Soluția a fost eliminarea completă a loading.tsx de la nivelul de produs și mutarea apelurilor de date direct în Server Components mici, împachetate individual în tag-uri <Suspense>.

Ce se întâmplă tehnic în browser:

  1. Serverul începe să execute pagina și randează imediat shell-ul HTML static și componentele rapide.
  2. Serverul trimite primul chunk HTTP prin streaming (Transfer-Encoding: chunked). Utilizatorul vede produsul aproape instant (TTFB sub 80ms, FCP imediat).
  3. Componenta lentă de reviews suspendă execuția; serverul injectează fallback-ul din Suspense în stream.
  4. Când promise-ul bazei de date se rezolvă, serverul trimite un nou chunk HTML cu conținutul real plus un mic snippet de JS inline care înlocuiește fallback-ul direct în DOM, fără vreun re-render pe client.

Trade-off-ul sincer: riscul de CLS

Suspense granular nu e o baghetă magică gratuită. Principalul dezavantaj este Cumulative Layout Shift (CLS).

Dacă skeleton-ul pe care îl pui în fallback are o înălțime de 120px, iar lista finală de comentarii randează un bloc de 600px, pagina va împinge tot footer-ul în jos brusc în timp ce userul încearcă deja să citească specificațiile. La proiectul de mai sus, am pierdut două zile doar ca să măsurăm și să calibrăm fallback-urile cu containere cu min-height fix sau aspect-ratio dedicat.

loading.tsx câștigă atunci când vrei protecție simplă la tranziții majore de pagină fără să te complici cu designul skeleton-urilor parțiale. Suspense explicit câștigă de fiecare dată când ai date lente non-critice care n-ar trebui să întârzie afișarea conținutului principal.

Voi la ce granularitate ați ajuns cu Suspense în producție? Împachetați fiecare widget în parte sau lăsați convențiile de rută să facă treaba?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.