// Prefetch inteligent la mouse hover sau focus
const ExportReportModal = React.lazy(() => import('./ExportReportModal'));
export const TriggerExportButton = () => {
const [isOpen, setIsOpen] = React.useState(false);
// Începem descărcarea chunk-ului înainte de click
const prefetchChunk = () => {
import('./ExportReportModal');
};
return (
<>
<button
onMouseEnter={prefetchChunk}
onFocus={prefetchChunk}
onClick={() => setIsOpen(true)}
>
Generează Raport
</button>
{isOpen && (
<React.Suspense fallback={<Spinner />}>
<ExportReportModal onClose={() => setIsOpen(false)} />
</React.Suspense>
)}
</>
);
};Aud des recomandarea: „pune React.lazy() sau dynamic import pe toate rutele și modalele, că scade bundle-ul inițial”. Am căzut și eu în capcana asta pe o aplicație B2B cu vreo 15k utilizatori activi zilnic. Rezultatul după un refactoring agresiv? First Contentful Paint a scăzut cu doar 80ms, dar Time to Interactive pe ecranele secundare a explodat cu peste 400ms pe conexiuni mobile.
Separarea codului vine cu un preț invizibil pe care mulți îl ignoră până când se uită într-un trace de Network în Chrome.
Iluzia celor „câțiva kilobaiți salvați”
Când extragi o componentă de 8KB într-un chunk separat, te gândești că ai ușurat încărcarea paginii. Realitatea e că ai înlocuit o descărcare liniară cu un nou request HTTP.
Chiar și pe HTTP/2 sau HTTP/3, unde multiplexarea elimină limitarea clasică de 6 conexiuni simultane, costul rămâne. Fiecare request implică latență de rețea (RTT), procesare în server, SSL overhead dacă sar pe alt domeniu și, cel mai important, timing-ul execuției. Dacă chunk-ul tău depinde de alt sub-chunk, declanșezi un frumos waterfall: browserul parsează primul fișier, abia apoi își dă seama că mai are nevoie de încă trei.
În plus, algoritmul de compresie (Brotli sau Gzip) funcționează mult mai prost pe fragmente minuscule. Un fișier unitar de 150KB comprimat are un dicționar partajat eficient; dacă îl spargi în zece fișiere de 15KB, suma lor arhivată ajunge adesea la 180-200KB din cauza redundanței.
Cum calculezi break-even-ul
Ca să decizi dacă o componentă merită dynamic import, ecuația este destul de simplă:
Cost descărcare inițială vs (Probabilitate accesare × Timp latență fetch) + Cost compresie pierdută
În practică, eu folosesc trei reguli empirice înainte să scriu import():
- Pragul de mărime: Dacă modulul are sub 25-30KB minified (fără gzip), nu-l sparg separat. Nu merită latența unui round-trip pe o conexiune 4G mediocră (care adaugă lejer 100-250ms) doar ca să salvez o fracțiune de secundă la primul parse.
- Probabilitatea de interacțiune: Dacă mai mult de 70% dintre useri ajung să deschidă acel element în primele 30 de secunde (de exemplu, primul tab dintr-un profil sau meniul principal de căutare), codul ăla trebuie să fie în bundle-ul principal sau preîncărcat imediat.
- Greutatea dependențelor terțe: Aici se schimbă radical ecuația. Dacă o componentă trage după ea un parser de Markdown (50KB), o librărie de charting (D3/Chart.js la 120KB) sau un editor text, atunci dynamic import e obligatoriu, indiferent cât de des e folosită.
Un compromis funcțional: prefetch la hover sau idle
Dacă ai un dialog greu de export sau un formular complex pe care userul îl accesează doar ocazional, nu aștepta click-ul final. Inițiază descărcarea dinamică când există o intenție clară — de exemplu, când userul trece cu mouse-ul peste butonul care va deschide componenta sau prin requestIdleCallback imediat după ce pagina a terminat prioritățile critice.
Trade-off-ul e direct: obții un bundle inițial curat, dar fără să-ți pedepsești utilizatorii cu un spinner de 300ms fix când dau click pe acțiune.
Voi la ce threshold de kilobaiți trageți linia pentru dynamic imports în proiectele voastre?