eduardweb.
PerformanceIntermediar#performance#vite#web-dev#javascript#webpack

Bundle splitting agresiv: cum am stricat performanța cu prea multe dynamic import-uri

De Paul Ene, 25 iul. 2026 · 10 vizualizări · 3 like-uri

Postat 25 iul. 2026
typescript
import React, { ComponentType, lazy } from 'react';

// Pattern simplu de lazy-loading cu capacitate de pre-încărcare la hover
export function lazyWithPreload<T extends ComponentType<any>>(
  factory: () => Promise<{ default: T }>
) {
  const Component = lazy(factory);
  (Component as any).preload = factory;
  return Component as typeof Component & { preload: () => void };
}

// Definim o componentă grea (ex: un editor sau un chart > 50KB)
const HeavyEditor = lazyWithPreload(() => import('./HeavyEditor'));

export const DashboardTab = () => {
  return (
    <div>
      {/* Declanșăm download-ul când userul duce mouse-ul peste buton, economisind 200ms */}
      <button 
        onMouseEnter={() => HeavyEditor.preload()}
        onClick={() => /* schimba starea pentru a afisa editorul */ null}
      >
        Deschide Editor
      </button>
    </div>
  );
};

Am văzut prea multe aplicații unde devii pun React.lazy() sau import() pe fiecare modal și tab secundar, crezând că fac un serviciu utilizatorilor. Am făcut și eu greșeala asta acum vreo 3 ani la un dashboard B2B cu 12.000 de utilizatori zilnici. Rezultatul? Un Lighthouse score impecabil pe First Load și un UX absolut mizerabil imediat ce omul începea să navigheze.

Adevărul e că dynamic import-ul nu e gratuit. Vine la pachet cu un cost de rețea și parsing pe care mulți îl ignoră.

Unde am dat de zid cu mini-chunk-urile

În proiectul de care vă ziceam, am configurat bundler-ul să spargă totul ultra-granular. Am ajuns la peste 90 de fișiere .js generate în build output. Unele aveau sub 3KB gzipped.

Teoretic, încărcam doar ce trebuia. Practic, la fiecare accesare de pagină secundară, browserul făcea o cascadă (waterfall) de 4-5 request-uri HTTP pentru scripturi mici. Deși foloseam HTTP/2, latența de rețea pe conexiuni mobile sau Wi-Fi aglomerat adăuga 150-300ms întârziere la fiecare click. Userul vedea spinner-e peste tot pentru componente banale.

Overhead-ul ascuns pe care nu-l măsoară Lighthouse

Când faci bundle splitting agresiv, plătești trei taxe ascunse:

  1. Request RTT (Round Trip Time): Chiar dacă fișierul are 2KB, handshaking-ul, DNS-ul și latența fizică durează. 10 request-uri de 2KB se încarcă mult mai greu decât un singur request de 20KB.
  2. Execution Context & Parsing: Browserul trebuie să evalueze și să parsing-uiască fiecare module script separat. Trecerea prin Event Loop pentru fiecare chunk nou adaugă jank pe main thread.
  3. Cache Invalidation masivă: Dacă ai 100 de chunk-uri minuscule legate între ele prin hărți de dependențe, o schimbare într-un utilitar comun va invalida hash-ul la 30 de chunk-uri simultan. Utilizatorul le va descărca din nou pe toate.

Cum calculezi punctul de break-even

Trade-off-ul e simplu: mărimea economisită la bundle-ul inițial trebuie să fie mai mare decât costul de latență al request-ului separat.

După mai multe teste și măsurători cu Performance API în producție, am stabilit o regulă empirică pentru echipa noastră:

  • Fișiere sub 20-30KB gzipped: Nu merită aproape niciodată chunk separat. Mai bine le lași în main bundle sau în chunk-ul rutei părinte.
  • Librării grele (Chart.js, Leaflet, Monaco Editor, PDF-renderers): Aici break-even-ul e garantat. O librărie de 150KB scoasă din bundle-ul principal salvează masiv din TTI (Time to Interactive).
  • Route-level splitting: Asta e singura regulă care trebuie aplicată aproape 100% din timp. Nivelul de rută e granița firească de încărcare.

Dacă chiar trebuie să spargi o componentă grea din interiorul unei pagini, folosește pattern-ul de preload on intent (la hover pe buton sau la focus), astfel încât request-ul să plece înainte ca userul să dea click.

Noi am redus timpul de încărcare perceput pe paginile interne cu 40% pur și simplu combinând 45 de mini-chunk-uri în 6 bundle-uri logice mai mari. Am pierdut 3 puncte în Lighthouse pe mobil, dar am câștigat o aplicație care se simte instantanee.

Voi ce praguri sau euristici folosiți când setați splitChunks în Webpack sau Vite? Mutați componente la nivel de UI în chunk-uri separate sau vă opriți doar la rute?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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