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

React Suspense granular vs loading.tsx în Next.js: De ce streaming SSR nu e magic

De Corina Dobre, 6 aug. 2026 · 7 vizualizări · 2 like-uri

Postat 6 aug. 2026
typescript
import { Suspense } from 'react';
import ProductDetails from './ProductDetails';
import ProductReviews from './ProductReviews';
import ReviewsSkeleton from './ReviewsSkeleton';

export default function ProductPage({ params }: { params: { id: string } }) {
  return (
    <main className="p-6 grid grid-cols-1 md:grid-cols-2 gap-4">
      {/* ProductDetails e rapid, face render direct */}
      <ProductDetails id={params.id} />
      
      {/* Fetch-ul lent e izolat în componentă și wrapped în Suspense */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews id={params.id} />
      </Suspense>
    </main>
  );
}

Salutare tuturor. Dacă lucrezi cu React 18 și Next.js App Router, e extrem de tentant să trântești un fișier loading.tsx într-un folder de rută și să consideri că ai terminat treaba cu streaming-ul. Am făcut și eu asta la început, dar pe măsură ce aplicația crește, realizăm că loading.tsx este doar o soluție de avarie — un topor acolo unde aveai nevoie de un bisturiu.

Câștigul direct din înțelegerea granularității Suspense este că nu mai blochezi render-ul elementelor rapide doar pentru că un microserviciu obosit răspunde în 800ms.

Ce face loading.tsx cu adevărat sub capotă

Next.js împachetează automat tot fișierul page.tsx (și copiii lui) într-un boundary <Suspense fallback={<Loading />}>. Sună bine în documentație, dar asta înseamnă că dacă page.tsx este o componentă async care face await fetch() la nivelul superior, tot layout-ul paginii respective așteaptă acel fetch înainte să arate orice bucată din HTML-ul paginii.

Dacă ai un banner de produs care vine în 20ms și un widget de recenzii care vine în 600ms, codul scris la nivel de pagină le va lega pe ambele de cel mai lent răspuns. loading.tsx va afișa skeleton-ul general pentru TOATĂ pagina, iar când cel mai lent await termină, abia atunci tot HTML-ul apare dintr-odată. Asta distruge complet ideea de Streaming SSR.

Suspense granular: mutarea fetch-ului în Server Components

Ca să profiți de streaming real, regula de aur pe care am aplicat-o la un proiect cu peste 15k pagini dinamice de e-commerce a fost simplă: nu mai face await în pagina principală (page.tsx).

Coboară fetch-ul în componentele individuale care au nevoie de acele date. Fiecare din aceste componente devine un Server Component async separat, iar în page.tsx le împachetezi individual în <Suspense>.

Cu această abordare, serverul trimite instant HTML-ul pentru structura principală și skeleton-urile pentru recenzii sau recomandări. Pe măsură ce microserviciile răspund pe backend, React face stream la bucățile rămase de HTML direct prin conexiunea HTTP deschisă, fără să hidrateze totul deodată.

Trade-off-uri reale: Nu totul e roz

Nu vă aruncați să puneți <Suspense> în jurul fiecărui div. Am pățit-o pe o altă aplicație și am creat un deșert de spinner-e care flickering-uiau în momente diferite, distrugând complet experiența utilizatorului și scorul CLS (Cumulative Layout Shift).

  1. Layout Shift masiv: Dacă skeleton-ul are 100px înălțime și componenta final randată are 400px, pagina va sări urât când vin datele. Trebuie să potrivești la pixel înălțimea fallback-ului.
  2. Waterfalls ascunse: Dacă un Suspense este cuibărit în alt Suspense și ambele depind de promișiuni care se rezolvă secvențial pe server, îți dublezi latența fără să-ți dai seama.

Când folosești fiecare?

Folosesc loading.tsx doar la nivel de layout global (ex: la schimbarea de rute mari gen /dashboard -> /settings) unde e normal ca întreaga vedere să se schimbe. Pentru orice pagină cu mai mult de o sursă de date, trec direct la <Suspense> granular în JSX.

La noi, schimbarea asta pe zona de recenzii a scăzut FCP-ul (First Contentful Paint) cu aproape 400ms, iar utilizatorii simt că pagina se încarcă instant, chiar dacă rating-urile apar o fracțiune de secundă mai târziu.

Voi cum procedați? Lăsați loading.tsx să-și facă de cap sau spargeți totul în boundary-uri dedicate?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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