eduardweb.
PerformanceIntermediar#performance#vite#javascript#bundling

De ce dynamic import-ul abuzat îți strică performanța și cum calculezi pragul de rentabilitate

De Teodor Pascu, 21 iul. 2026 · 9 vizualizări · 2 like-uri

Postat 21 iul. 2026
javascript
// Exemplu de grupare manuală în Vite pentru a evita fragmentarea excesivă
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // Grupăm librăriile mici de utilități într-un singur chunk stabil
          if (id.includes('node_modules/lodash') || id.includes('node_modules/date-fns')) {
            return 'utils-shared';
          }
          // Evităm crearea de micro-chunk-uri pentru fiecare fișier de limbă
          if (id.includes('src/locales/')) {
            return 'i18n-bundle';
          }
        }
      }
    }
  }
});

Toată lumea îți spune să pui dynamic import() peste tot ca să scazi dimensiunea bundle-ului inițial și să obții scoruri verzi în Lighthouse. Am mușcat și eu momeala asta la un proiect cu vreo 12k utilizatori zilnici și am reușit performanța să fac aplicația să se simtă mult mai lentă pe telefoane mid-range.

Hai să vorbim pe cifre și pe ce se întâmplă real în rețea, fără povești de marketing.

Costul ascuns al fragmentării (The Waterfall Effect)

Marea iluzie a HTTP/2 (și HTTP/3) este că multiplexarea face numărul de request-uri irelevant. „Trimite 100 de fișiere mici, e totuna cu unul mare”, zic unii. Complet fals în practică.

Fiecare request nou, chiar și pe o conexiune deja deschisă, vine cu un overhead. Browserul trebuie să parseze headerele, să facă prioritizarea resurselor și să aștepte ca serverul să trimită pachetele. Pe o conexiune mobilă cu un RTT (Round Trip Time) de 100ms, orice request suplimentar care depinde de execuția altui script adaugă latență vizibilă.

Am pățit-o concret: aveam un formular de checkout unde fiecare pas (3 pași în total) era încărcat dinamic. Utilizatorul dădea click pe „Pasul următor” și ecranul îngheța 300-400ms pentru că browserul abia atunci descărca chunk-ul pentru pasul 2, deși acel chunk avea doar 4KB gzipped. E un UX mizerabil.

Cum calculezi matematic break-even-ul?

Ca să nu mai mergi pe ghicite, există o formulă simplă de bun simț pe care o aplic acum. Merită să splituiești un modul doar dacă timpul de descărcare + latența rețelei este semnificativ mai mic decât timpul economisit la încărcarea inițială a paginii.

Să facem un calcul rapid pentru un modul de 15KB gzipped (cam 45KB necomprimat):

  1. Dacă e în bundle-ul principal: Adaugă ~1.5ms la timpul de parsing pe un procesor mobil mediu și poate 50ms la download-ul inițial.
  2. Dacă e splituit dinamic: Când utilizatorul are nevoie de el, browserul face un request. Pe o conexiune 4G medie, latența (RTT) e de ~60ms. Descărcarea celor 15KB durează încă 20ms. Timp total de așteptare pentru user: 80ms de ecran blocat sau spinner.

Dacă modulul are sub 20-30KB gzipped și este folosit în 80% din sesiuni, nu îl splitui. Costul latenței de rețea depășește cu mult beneficiul celor câteva milisecunde economisite la prima încărcare a paginii.

Cum rezolvi asta în build tool-uri (Vite / Webpack)

Implicit, Vite/Rollup tinde să creeze chunk-uri foarte fragmentate dacă folosești multe biblioteci terțe și dynamic imports. Soluția este să grupezi chunk-urile mici în ceva mai consistent folosind configurația de build.

La proiectul menționat, am redus numărul de request-uri la load de la 45 la 12 combinând dependențele mici și paginile secundare în chunk-uri comune. Am economisit cam 30% din timpul de interactivitate (TTI) pe mobil.

Tu ce prag folosești pentru dynamic imports? Împărți totul la nivel de rută sau mergi pe bundle-uri mai mari și mai puține?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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