import React, { useState } from 'react';
// Componentă helper care încarcă codul la hover
const LazyComponentWithHover = () => {
const [Component, setComponent] = useState(null);
const preloadComponent = () => {
if (!Component) {
import('./HeavyChartComponent').then((module) => {
setComponent(() => module.default);
});
}
};
return (
<div onMouseEnter={preloadComponent} onFocus={preloadComponent}>
<button onClick={() => !Component && preloadComponent()}>
Arată Graficul
</button>
{Component && <Component />}
</div>
);
};Am trecut cu toții prin faza în care am descoperit import() și am vrut să facem split la absolut tot din aplicație. Pare logic la prima vedere: trimiți mai puțini octeți la încărcarea inițială, deci site-ul zboară. În realitate, dacă abuzezi de dynamic imports, s-ar putea să transformi o aplicație rapidă într-un coșmar de latențe pe mobil.
Ce pierzi când ai prea multe chunk-uri
Fiecare dynamic import generează un fișier JS separat. Când userul face o acțiune care cere acel chunk, browserul trebuie să facă un nou request HTTP. Chiar și pe HTTP/2 sau HTTP/3, unde multiplexarea ne scapă de blocajele vechi de conexiune, tot ai de-a face cu latența fizică a rețelei.
Am avut un caz la un proiect cu 15k useri activi, unde dashboard-ul avea dynamic import pe fiecare modal și tab secundar. Sună bine pe hârtie, nu? În practică, utilizatorii de pe 3G/4G așteptau câte 1.5 - 2 secunde după ce dădeau click pe "Editează Profilul". Browserul trebuia să facă DNS lookup, TCP handshake, negocieri SSL și abia apoi să descarce un amărât de chunk de 3KB.
Trade-off-ul e simplu: reduci timpul de încărcare inițial (FCP/LCP), dar distrugi interactivitatea ulterioară (FID/INP). Utilizatorul simte aplicația ca fiind leneșă și instabilă, chiar dacă scorul de Lighthouse pe landing page e de 99.
Cum calculăm pragul de rentabilitate (Break-Even)
Nu există o formulă magică universală, dar am o regulă empirică pe care o folosesc de ani de zile și care dă rezultate excelente. Pentru a decide dacă merită să separi un cod într-un chunk dinamic, pun în balanță dimensiunea codului și probabilitatea de accesare.
Iată logica mea de selecție:
- Dacă fișierul are sub 20KB (gzipped): Nu merită aproape niciodată dynamic import. Costul unui request HTTP suplimentar pe o conexiune mobilă medie (cu un RTT de 100-200ms) depășește cu mult timpul necesar pentru a descărca acei 20KB în bundle-ul principal.
- Dacă probabilitatea ca userul să acceseze acea secțiune e peste 80%: Îl lași în bundle-ul principal. Nu are sens să faci split la pagina de dashboard principală dacă 9 din 10 useri ajung direct acolo.
- Cazul ideal pentru split: Componente mari (peste 50KB) folosite rar (sub 20% dintre sesiuni). Exemple clasice: editorul de text îmbogățit (WYSIWYG), librăriile de grafice (cum e D3 sau Chart.js) sau modalul de export PDF.
Cum atenuezi impactul: Preloading inteligent
Dacă totuși trebuie să faci split pentru o componentă medie, nu lăsa utilizatorul să aștepte după click. Cea mai bună metodă este să începi încărcarea chunk-ului în fundal, de exemplu când userul face hover peste butonul care deschide acea secțiune.
Am implementat schema asta simplă în React și am redus latența percepută la zero pentru majoritatea tab-urilor din aplicație, economisind totodată vreo 35% din bundle-ul inițial.
Bundle splitting-ul nu este o soluție universală bună. Dacă împarți aplicația în 100 de bucățele de câte 2KB, ai pierdut startul din cauza overhead-ului de rețea. Limitează-te la bibliotecile mari și la rutele secundare pe care userul chiar le vizitează rar.
Voi cum procedați? Aveți o limită clară de KB de la care decideți să faceți lazy load sau mergeți pur și simplu pe feeling?