eduardweb.
PerformanceIntermediar#performance#react#javascript#webpack

Mitul bundle-splitting-ului: Când dynamic import-ul îți încetinește aplicația

De Vlad Stancu, 9 iul. 2026 · 14 vizualizări · 3 like-uri

Postat 9 iul. 2026
typescript
import React, { useState } from 'react';

// Folosim directiva webpackPrefetch pentru a descărca chunk-ul în fundal
// imediat ce thread-ul principal este liber. Elimină latența la click.
const HeavyChart = React.lazy(() =>
  import(/* webpackPrefetch: true */ './components/HeavyChart')
);

export function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        Afișează Grafic
      </button>
      {showChart && <HeavyChart />}
    </div>
  );
}

Am observat o tendință destul de periculoasă în ultima vreme: cum apare o pagină nouă în proiect sau un modal mai mare, pac, punem dynamic import sau React.lazy pe ea. Sună logic pe hârtie, nu? Micșorăm bundle-ul inițial, încărcăm doar ce trebuie și când trebuie. Am făcut și eu prostia asta acum vreo trei ani la un proiect cu vreo 15.000 de utilizatori activi zilnic. Am trecut de la un bundle mare de 1.5MB la vreo 35 de chunk-uri mici.

Rezultatul? Scorurile de Lighthouse au scăzut, iar utilizatorii de pe mobil cu conexiuni mai slabe ne trimiteau tichete că interfața "se blochează" când dau click pe butoane. Hai să demontăm mitul ăsta și să vedem de ce mai multe fișiere nu înseamnă mereu viteză mai mare.

Costul ascuns al fragmentării extreme

Fiecare dynamic import generează un fișier .js separat în build-ul final. Când browserul dă de el în cod, trebuie să facă un request HTTP suplimentar. Da, știu, avem HTTP/2 și HTTP/3 cu multiplexare, dar asta nu înseamnă că latența rețelei a dispărut prin magie.

Fiecare request are un overhead. Există timpul de DNS lookup, conexiunea TCP, negocierea TLS și, cel mai important, latența fizică (RTT - Round Trip Time) de pe rețelele mobile, care pe 4G sau 3G poate depăși lejer 150-200ms. Dacă ai spart aplicația în 30 de chunk-uri mici de câte 5KB, browserul va petrece mai mult timp făcând handshake-uri și așteptând headerele HTTP decât rulând codul efectiv.

În plus, riști să creezi un "waterfall" de rețea. Chunk-ul A se descarcă, se execută, și abia atunci browserul realizează că are nevoie de chunk-ul B, care la rândul lui importă chunk-ul C. Din două request-uri care puteau fi paralele, ai făcut trei secvențiale, dublând sau triplând timpul de așteptare.

Cum calculezi pragul de break-even

Regula mea de aur, învățată pe pielea mea după ce am stricat performanța acelei aplicații, este extrem de simplă: nu sparge nimic sub 30KB (gzipped). Pur și simplu nu merită efortul de rețea pentru câțiva kiloocteți.

Pentru a decide dacă un dynamic import își merită existența, folosesc o ecuație rapidă bazată pe două variabile:

  1. Frecvența de utilizare: Câți dintre utilizatorii care ajung pe pagina X vor accesa componenta respectivă? Dacă e un modal de setări avansate pe care doar 5% din oameni îl deschid, merită split-ul. Dacă e un tab de navigare principal pe care dau click 90% din oameni, dynamic import-ul e o greșeală.
  2. Dimensiunea librăriilor externe: Dacă ai o pagină cu un grafic imens (de exemplu, Chart.js de ~150KB) sau un editor de text complex (cum e Quill de ~200KB), acela este candidatul ideal. Utilizatorul de pe landing page nu trebuie să tragă după el acele librării grele.

Trade-off-ul UX: Initial Load vs. Interaction Delay

Aici e marea capcană. Prin bundle splitting agresiv, optimizezi metrici precum FCP (First Contentful Paint) sau LCP (Largest Contentful Paint) pentru roboții de la Google. Arată bine în rapoartele de SEO, dar distrugi complet INP (Interaction to Next Paint) sau fostul FID (First Input Delay) pentru utilizatorii reali.

Merge bine pentru landing-uri sau pagini de prezentare unde vrei doar să se încarce textul repede. E absolut nasol pentru aplicații de tip dashboard (SaaS) unde utilizatorul vrea interacțiuni fluide. E o experiență mizerabilă să ai un loader pe ecran pentru fiecare click pe un buton banal doar pentru că dezvoltatorul a vrut să reducă bundle-ul inițial cu 12KB.

O soluție tehnică elegantă pentru a atenua asta este folosirea directivelor de prefetch. Descărcăm chunk-ul în fundal, la prioritate mică, imediat ce browserul s-a eliberat. Când userul dă click, codul e deja în memoria cache.

Voi cum gestionați treaba asta în producție? Mergeți pe intuiție sau aveți reguli stricte setate în bundler pentru dimensiunea minimă a chunk-urilor?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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