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

React Suspense vs loading.tsx în Next.js: Când folosești granularitate și cum faci Streaming SSR

De Vlad Stancu, 25 iul. 2026 · 10 vizualizări · 3 like-uri

Postat 25 iul. 2026
typescript
import { Suspense } from 'react';
import { FastMetrics, SlowRecommendations, CardSkeleton } from '@/components';

// Pagina randată pe server fără loading.tsx global
export default async function DashboardPage() {
  return (
    <div className="grid gap-6 p-6">
      {/* Rendat instant la primul chunk SSR */}
      <FastMetrics />

      {/* Trimis prin stream ulterior pe aceeași conexiune HTTP */}
      <Suspense fallback={<CardSkeleton className="min-h-[300px]" />}>
        <SlowRecommendations />
      </Suspense>
    </div>
  );
}

Am văzut de prea multe ori echipe care migrează pe Next.js App Router și trântesc un loading.tsx în rădăcina fiecărui folder, crezând că au rezolvat performanța. E un început bun pentru UX-ul de navigare, dar în felul ăsta pierzi exact magia de streaming SSR pe care ți-o oferă React 18.

Hai să-ți arăt diferența practică dintre cele două abordări și unde ne-am lovit noi de zid la o aplicație cu 12k useri activi zilnic.

Ce face de fapt loading.tsx sub capotă

Fișierul loading.tsx este doar un syntactic sugar oferit de Next.js. Când îl pui într-un folder de rută, cadrul îți îmbracă automat fișierul page.tsx (și toți copiii lui) într-un <Suspense fallback={<Loading />}>. Atât.

Problema apare când pagina ta are trei componente rapide și una foarte lentă. Dacă te bazezi doar pe loading.tsx la nivel de pagină, utilizatorul vede un spinner pe tot ecranul până când și cel mai lent request de pe server termină de executat. Practic, te întorci la vechiul render-blocking SSR, doar că acum trimiți un skeleton screen în loc de un ecran alb.

Când cobori mai jos cu Suspense granular

Anul trecut refăceam un dashboard unde aduceam metrici financiare (query rapid de DB, ~40ms) și o listă de recomandări dintr-un serviciu extern de ML (lent, 800ms - 1.2s). Inițial aveam un singur loading.tsx pe rută. Rezultatul? TTFB-ul părea bun pentru că serverul răspundea repede cu skeleton-ul, dar user-ul aștepta peste o secundă să vadă orice cifră utilă.

Am șters loading.tsx-ul din ruta respectivă și am mutat boundaries-urile de Suspense direct în interiorul componentei de pagină, în jurul fiecărui bloc de date.

Efectul a fost imediat. Serverul a trimis primul chunk de HTML cu metricile financiare deja gata de afișat în sub 100ms. Pentru blocul lent, React a păstrat conexiunea HTTP deschisă (folosind HTTP Chunked Encoding) și a făcut stream direct pe rețea la fallback-ul de skeleton, urmat de codul HTML final și scriptul de hidratare exact când promisiunea s-a rezolvat pe server. Am tăiat percepția timpului de așteptare cu aproape 60%.

Trade-off-uri reale: Layout shift și Waterfall-uri

Să nu crezi că Suspense granular e un glonț de argint. Vine la pachet cu provocări de arhitectură:

  1. Cumulative Layout Shift (CLS): Dacă scheletul tău de fallback are 100px înălțime și componenta finală randată are 400px, conținutul paginii va sări urât în jos când vin datele. Pe mobil e o experiență groaznică. Trebuie să măsori exact containerul.
  2. Waterfall-uri mascate: Dacă ai un Server Component asincron randat în interiorul altui Server Component asincron, fiecare fiind învelit în Suspense, apelurile de rețea se vor executa secvențial pe server. Dacă nu folosești Promise.all la nivelul părintelui sau nu restructurezi componentele paralel, dublezi timpul total de răspuns.

Regula mea după ultimele proiecte: folosesc loading.tsx strict pentru tranzițiile mari de navigare între pagini distincte, iar <Suspense> granular direct în pagină pentru widgets, tabele grele sau componente legate de microservicii externe.

Voi cum gestionați diferența de înălțime la fallback-uri ca să evitați CLS-ul? Puneți dimensiuni fixe pe skeleton sau lăsați layout-ul să fie flexibil?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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