eduardweb.
PerformanceIntermediar#performance#react#javascript#webpack

Bundle splitting cu dynamic import: ce pierzi când abuzezi și cum calculezi pragul rentabilității

De Cosmin Rotaru, 8 iul. 2026 · 14 vizualizări · 3 like-uri

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

// Definim loader-ul manual pentru a-l putea apela pe hover
const importModal = () => import('./LargeModal');
const LazyModal = React.lazy(importModal);

export function ActionButton() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="p-4">
      <button
        onMouseEnter={importModal} // Pornim descărcarea codului când userul duce mouse-ul pe buton
        onClick={() => setIsOpen(true)}
        className="px-4 py-2 bg-blue-600 text-white rounded"
      >
        Deschide Raport Detaliat
      </button>

      {isOpen && (
        <React.Suspense fallback={<div>Se încarcă modulul...</div>}>
          <LazyModal />
        </React.Suspense>
      )}
    </div>
  );
}

Am văzut prea des recomandarea asta oarbă pe grupuri: „pune dynamic import peste tot ca să ai scor maxim în PageSpeed”. E o capcană care distruge UX-ul dacă este aplicată fără discernământ. Astăzi vreau să vorbim despre când devine fragmentarea codului contraproductivă și cum calculezi pragul de la care merită să spargi bundle-ul în bucăți mai mici.

Costul ascuns al fragmentării excesive

La un proiect SaaS cu vreo 12.000 de utilizatori activi pe lună, am vrut să fim „băieții deștepți” de pe frontend. Am configurat bundler-ul să facă chunk-uri separate din aproape orice: fiecare modal, fiecare dropdown complex și fiecare utilitar de formatat date. Rezultatul imediat? Pagina principală se încărca cu 150ms mai repede pe mobil.

Partea proastă? Prima interacțiune a utilizatorului devenise mizerabilă. Fiecare import() înseamnă o cerere HTTP suplimentară. Chiar și pe HTTP/2 sau HTTP/3, conexiunea are un cost de negociere. Pe o rețea 3G sau 4G instabilă, când userul dădea click pe un buton simplu care deschidea un modal de export, aștepta peste o secundă doar ca browserul să aducă acel chunk de 8KB.

Practic, am mutat timpul de așteptare de la încărcarea inițială direct în mijlocul experienței de utilizare. Și crede-mă, un spinner apărut din senin pe un click de buton e mult mai enervant decât 100ms în plus la primul load.

Cum calculezi break-even-ul?

Nu există o formulă magică în documentații, dar noi am implementat o metodă empirică bazată pe două variabile: dimensiunea chunk-ului și rata de utilizare a feature-ului respectiv.

Să luăm un exemplu concret: o librărie de grafice care are 40KB gzipped.

  • Scenariul A (In-bundle): Adaugă aproximativ 80-100ms la timpul de descărcare inițial pe o conexiune mobilă medie. Toți utilizatorii „plătesc” această taxă.
  • Scenariul B (Dynamic import): Economisești acele 100ms la început. Însă, când utilizatorul accesează secțiunea de statistici, browserul face cererea de rețea. Pe un mobil cu latență de 150ms, descărcarea celor 40KB va dura în total în jur de 450ms. Utilizatorul vede un ecran alb sau un schelet de încărcare.

Cum decidem? Ne uităm în Analytics. Dacă feature-ul de grafice este accesat de peste 40% dintre utilizatori în prima sesiune, dynamic import-ul este o greșeală. Plătești prea des taxa de latență la interacțiune. Dacă rata de accesare este de sub 15%, atunci izolarea într-un chunk separat este complet justificată.

Regula mea de aur pentru splitting

După ce am refăcut structura bundle-urilor și am redus numărul de request-uri cu 30%, am rămas cu trei reguli simple pe care le aplicăm la orice proiect:

  1. Rutele din router: Întotdeauna dynamic import. Utilizatorul se așteaptă la o mică tranziție când schimbă pagina.
  2. Componentele grele (peste 30KB gzipped): Doar dacă nu sunt vizibile direct la primul scroll (de exemplu, un editor de text bogat sau o hartă interactivă).
  3. Elemente de UI mici (modale de confirmare, tooltips, dropdowns): Rămân în bundle-ul principal. Costul latenței de rețea este mult mai mare decât economia de câțiva kiloocteți.

Dacă totuși ești obligat să folosești dynamic import pentru o componentă care trebuie să se deschidă instantaneu, aplică un trigger de prefetch. Cel mai simplu mod este să începi descărcarea fișierului pe evenimentul de onMouseEnter al butonului care deschide acea componentă, câștigând astfel câteva sute de milisecunde prețioase înainte de click.

Voi cum gestionați echilibrul ăsta? Mergeți pe splitting agresiv sau preferați un bundle principal mai gras, dar care oferă interacțiuni instantanee?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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