JavaScript bundle optimalizálás 2026: code splitting, tree shaking és modern bundler stratégiák

Hogyan csökkentsd a JavaScript bundle méretet 2026-ban? Code splitting, tree shaking, Vite 8/Rolldown és INP optimalizálás — gyakorlati kódpéldákkal és valós esettanulmánnyal.

Miért a JavaScript bundle méret a webes teljesítmény Achilles-sarka?

Ha fejlesztőként dolgozol, valószínűleg te is érezted már: az alkalmazásod egyre lassabb, a bundle egyre nagyobb, a felhasználók pedig egyre türelmetlenebbek. Nem vagy ezzel egyedül. A HTTP Archive 2025-ös Web Almanac adatai szerint a medián weboldal 23 JavaScript kérést indít asztali gépen és 22-t mobilon, miközben az oldalméretek mediánja elérte a 2,9 MB-ot desktopra — ami 7,3%-os növekedés 2024-hez képest. Ami igazán elgondolkodtató: a medián weboldal 280 KB kihasználatlan JavaScript kódot szállít le asztali gépen, mobilon pedig 251 KB-ot. A 90. percentilisnél ez meghaladja az 1 MB-ot.

Ezek a számok egyértelműek: a JavaScript bundle optimalizálás 2026-ban nem opcionális luxus, hanem a webes teljesítmény egyik legkritikusabb területe. Különösen most, amikor a Google Core Web Vitals mutatói — köztük az INP (Interaction to Next Paint) — közvetlenül befolyásolják a keresési rangsorolást.

Na de elég a riogatásból. Ebben az útmutatóban végigmegyünk a JavaScript bundle optimalizálás minden fontos aspektusán: a code splitting alapjaitól a tree shaking finomságain át a modern bundler-ek lehetőségeiig. Gyakorlati kódpéldákat és mérhető eredményeket mutatok be, amelyeket azonnal alkalmazhatsz a projektjeidben.

A JavaScript bundle probléma anatómiája

Mielőtt az optimalizálási technikákba merülünk, érdemes megérteni, miért is olyan problémás egy nagy JavaScript bundle. A böngészőnek három fő lépésen kell végigmennie minden letöltött JavaScript fájllal:

  1. Letöltés: A fájl letöltése a hálózaton keresztül. Lassú mobilhálózaton egy 500 KB-os tömörített bundle is több másodpercet igényelhet.
  2. Elemzés (parsing): A böngészőnek le kell elemezni a JavaScript forráskódot és AST-vé (Abstract Syntax Tree) kell alakítania. Ez CPU-intenzív művelet, ami különösen régebbi mobilokon fáj.
  3. Végrehajtás (execution): A kód futtatása a fő szálon (main thread), ami közben blokkolja a felhasználói interakciókat.

A fő szál blokkolása közvetlen hatással van az INP mutatóra. Ha a felhasználó kattint, gördít vagy begépel valamit, miközben a böngésző egy nagy JavaScript bundle-t dolgoz fel, a válaszidő drámaian megnő. A Google elvárása, hogy az INP 200 ms alatt maradjon — és őszintén szólva, a felesleges JavaScript feldolgozás az egyik leggyakoribb ok, amiért az oldalak nem teljesítik ezt.

A JavaScript költségének mérése

Sokan csak a fájlméretet nézik, de a bundle méret önmagában nem árulkodik a valódi költségről. Egy 200 KB-os JavaScript fájl feldolgozása akár ötször annyi időt vehet igénybe, mint egy 200 KB-os kép dekódolása — mert a JavaScript-et nem csak le kell tölteni, hanem elemezni, fordítani és végrehajtani is kell. Ezt a Chrome DevTools Performance fülén, a Lighthouse Reduce unused JavaScript javaslatával, vagy a Coverage eszközzel (Ctrl+Shift+P → Show Coverage) tudod mérni.

// A bundle méret ellenőrzése build után - webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: false,
    }),
  ],
};

// Vite esetében: rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      filename: 'bundle-stats.html',
      gzipSize: true,
      brotliSize: true,
    }),
  ],
});

Code Splitting: csak azt töltsd be, amire tényleg szükség van

A code splitting (kódszétválasztás) talán a leghatékonyabb fegyver a JavaScript bundle méretének csökkentésére. Az alapelv szinte banálisan egyszerű: ahelyett, hogy egyetlen hatalmas fájlban szállítjuk le az alkalmazás teljes kódját, kisebb darabokra — úgynevezett chunk-okra — bontjuk, és csak azokat töltjük be, amelyekre az adott oldalon szükség van.

Útvonal-alapú code splitting (Route-based)

A legegyszerűbb és talán leghatékonyabb belépési pont az útvonal-alapú code splitting. SPA alkalmazásoknál minden útvonal saját chunk-ot kap, így csak az aktuálisan meglátogatott oldal kódja töltődik le.

// React - Route-based code splitting React.lazy()-vel
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Statikus import helyett dinamikus import
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div className="loading-spinner" />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/analytics" element={<Analytics />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Vue.js-ben ez még egyszerűbb (a Vue Router szinte kínálja magát erre):

// Vue Router - automatikus code splitting
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    component: () => import('./views/Home.vue'),
  },
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue'),
  },
  {
    path: '/analytics',
    // Named chunk a jobb debuggolásért
    component: () => import(/* webpackChunkName: "analytics" */ './views/Analytics.vue'),
  },
];

Komponens-szintű code splitting

Az útvonal-alapú szétválasztáson túl érdemes a nehéz komponenseket is külön chunk-ba szervezni. Tipikus jelöltek: modális ablakok, diagramok, szöveges editorok, dátumválasztók — lényegében minden, ami nem jelenik meg azonnal az oldal betöltésekor.

// Nehéz komponens lazy loading-ja
import React, { Suspense, lazy, useState } from 'react';

// Ez a chunk csak akkor töltődik le, amikor a felhasználó megnyitja a szerkesztőt
const RichTextEditor = lazy(() => import('./components/RichTextEditor'));
const ChartDashboard = lazy(() => import('./components/ChartDashboard'));

function ArticlePage({ article }) {
  const [isEditing, setIsEditing] = useState(false);
  const [showCharts, setShowCharts] = useState(false);

  return (
    <div>
      <article>{article.content}</article>

      <button onClick={() => setIsEditing(true)}>Szerkesztés</button>
      {isEditing && (
        <Suspense fallback={<p>Szerkesztő betöltése...</p>}>
          <RichTextEditor content={article.content} />
        </Suspense>
      )}

      <button onClick={() => setShowCharts(true)}>Statisztikák</button>
      {showCharts && (
        <Suspense fallback={<p>Diagramok betöltése...</p>}>
          <ChartDashboard data={article.stats} />
        </Suspense>
      )}
    </div>
  );
}

Vendor splitting: a harmadik féltől származó kód elkülönítése

A harmadik féltől származó könyvtárak (lodash, moment, chart.js stb.) ritkán változnak, amikor frissíted az alkalmazáskódot. Ha ezeket külön chunk-ba szervezed, a böngésző cache-ből szolgálhatja ki őket egy-egy deploy után is. Ez drasztikusan csökkenti a visszatérő felhasználók betöltési idejét — és ők alkotják a forgalmad nagy részét.

// webpack.config.js - Optimalizált vendor splitting
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // Fő framework chunk (ritkán változik)
        framework: {
          test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
          name: 'framework',
          priority: 40,
          enforce: true,
        },
        // Nagyobb könyvtárak külön chunk-ban
        largeVendors: {
          test: /[\\/]node_modules[\\/](chart\.js|lodash|moment|date-fns)[\\/]/,
          name: 'large-vendors',
          priority: 30,
          enforce: true,
        },
        // Minden egyéb vendor
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 20,
          reuseExistingChunk: true,
        },
        // Közös alkalmazáskód
        common: {
          minChunks: 2,
          priority: 10,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

Tree Shaking: szabadulj meg a felesleges kódtól

A tree shaking (szó szerint „fa rázás") a bundler azon képessége, hogy statikus elemzéssel felismeri és eltávolítja a nem használt kódot a végső bundle-ből. A metafora onnan ered, hogy ha megrázod a fát, a száraz ágak — vagyis a nem használt kódrészletek — lehullanak.

Egyszerűen hangzik, igaz? A gyakorlatban viszont van néhány buktatója.

A tree shaking működésének feltétele: ES modulok

A tree shaking kizárólag ES6 modul szintaxissal (import / export) működik, mert ez statikusan elemezhető. A CommonJS (require / module.exports) dinamikus természete miatt nem alkalmas rá — a bundler egyszerűen nem tudja kitalálni, hogy futásidőben mire lesz szükség.

// ❌ ROSSZ - CommonJS: nem tree-shakeable
const { debounce } = require('lodash');
// Ez az EGÉSZ lodash könyvtárat beemeli (~70 KB gzip)

// ✅ JÓ - ES modul: tree-shakeable
import { debounce } from 'lodash-es';
// Csak a debounce függvény és függőségei kerülnek a bundle-be

// ✅ MÉG JOBB - Közvetlen import
import debounce from 'lodash/debounce';
// Garantáltan csak a debounce kerül be

A sideEffects mező helyes konfigurálása

A package.json sideEffects mezője kritikus a hatékony tree shaking-hez. Ez mondja meg a bundler-nek, hogy a modul melyik fájljai rendelkeznek mellékhatásokkal, és melyeket lehet biztonságosan törölni, ha senki sem importálja őket.

// package.json
{
  "name": "my-library",
  "version": "1.0.0",
  "sideEffects": false,
  // VAGY specifikusan megjelölve a mellékhatással bíró fájlokat:
  "sideEffects": [
    "*.css",
    "*.scss",
    "./src/polyfills.js",
    "./src/analytics-init.js"
  ]
}

Ha a sideEffects értéke false, a bundler bátran eltávolíthat minden nem importált exportot. Ha viszont nincs beállítva? Akkor a bundler kénytelen konzervatívan viselkedni és mindent megtartani — ami feleslegesen duzzasztja a bundle-t.

Tree shaking problémák diagnosztizálása

A tree shaking sajnos nem mindig működik tökéletesen. Lássuk a leggyakoribb csapdákat:

// 1. probléma: Barrel exports akadályozzák a tree shaking-et
// ❌ src/components/index.js (barrel file)
export { default as Button } from './Button';
export { default as Modal } from './Modal';      // ~50 KB
export { default as DataGrid } from './DataGrid'; // ~120 KB
export { default as Chart } from './Chart';        // ~80 KB

// Ha csak a Button-t importálod:
import { Button } from './components';
// Egyes bundler konfigurációknál az ÖSSZES komponens bekerülhet!

// ✅ Megoldás: közvetlen import
import Button from './components/Button';

// 2. probléma: Dinamikus property hozzáférés
// ❌ Nem tree-shakeable
const icons = { home: HomeIcon, user: UserIcon, settings: SettingsIcon };
const Icon = icons[iconName]; // A bundler nem tudja, melyikre lesz szükség

// ✅ Megoldás: explicit import
import HomeIcon from './icons/Home';

// 3. probléma: Globális mellékhatások
// ❌ Ez a fájl nem törölhető, mert mellékhatása van
import './polyfills'; // Globális objektumot módosít
Array.prototype.customMethod = function() { /* ... */ };

Modern bundler-ek összehasonlítása 2026-ban

A JavaScript bundler ökoszisztéma 2026-ra alaposan átalakult. A Rust és Go nyelven írt, natív sebességű eszközök átvették a vezetést a korábbi JavaScript-alapú megoldásoktól. Nézzük a főbb szereplőket.

Vite 8 és Rolldown: az új alapértelmezett

A Vite 8 béta verziója hozta el a Rolldown integrációt — egy Rust-alapú bundler-t, amely a Rollup drop-in helyettesítőjeként működik, de 10-30x gyorsabb annál. Korábban a Vite fejlesztési módban esbuild-et, production build-hez pedig Rollup-ot használt. A Rolldown mindkettőt egységesíti egyetlen, natív sebességű eszközben. (Személyes véleményem, hogy ez volt az egyik legjobb lépés a Vite ökoszisztémában.)

// vite.config.js - Vite 8 optimalizált konfiguráció
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    // Rolldown automatikusan aktív Vite 8-ban
    target: 'es2022',
    // Chunk méret figyelmeztetés küszöbérték
    chunkSizeWarningLimit: 250, // KB
    rollupOptions: {
      output: {
        // Manuális chunk szétválasztás
        manualChunks: (id) => {
          if (id.includes('node_modules')) {
            // React ökoszisztéma külön chunk
            if (id.includes('react') || id.includes('react-dom')) {
              return 'react-vendor';
            }
            // Egyéb vendor-ok
            return 'vendor';
          }
        },
      },
    },
  },
});

Rspack: Webpack-kompatibilis Rust-alapú bundler

Ha meglévő Webpack projekttel dolgozol és nem akarod az egészet újraírni, az Rspack kiváló átmeneti megoldás. A legtöbb Webpack konfigurációval kompatibilis, miközben a build idők drámaian csökkennek — jellemzően 5-10x gyorsulás érhető el, ami nagyobb projekteken naponta akár órákat spórolhat a csapatnak.

Teljesítmény összehasonlítás (benchmark adatok)

Az rspack csapat által karbantartott build-tools-performance benchmark alapján a kép elég egyértelmű:

  • esbuild és Rolldown: 1 másodperc alatti build idő — az esbuild akár 250 ms alatt végez
  • Rspack: ~2,5 másodperc
  • Vite (Rollup-pal), Rollup, Webpack: 5+ másodperc
  • Bundle méret: Rollup, Vite, esbuild, Rolldown és Parcel hasonlóan kis bundle-t produkálnak (~2081–2162 KiB a benchmark projektben)

A tanulság: a build sebesség önmagában nem minden. A bundle méret és a tree shaking hatékonysága legalább annyira fontos. A Vite 8 (Rolldown-nal) itt kombinált előnyt kínál — natív sebesség és kiváló tree shaking egyben.

A fő szál tehermentesítése: INP optimalizálás JavaScript szinten

A bundle méret csökkentése csak a történet fele. A másik — és legalább ilyen fontos — fele az, hogyan futtatjuk a JavaScript kódot. Pontosabban: hogyan kerüljük el, hogy a fő szál (main thread) hosszú időre beragadjon, ami közvetlenül rontja az INP-t.

Hosszú feladatok szétbontása (Long Task breaking)

A böngésző fő szála egyszerre vagy JavaScript-et futtat, vagy a felhasználói interakciókra reagál — a kettő egyszerre nem megy. Ha egy JavaScript feladat 50 ms-nél tovább tart (ezt hívjuk „Long Task"-nak), a felhasználó érezhetően lassúnak fogja tapasztalni az oldalt.

A megoldás? Bontsuk kisebb darabokra a hosszú feladatokat, és adjunk lehetőséget a böngészőnek, hogy közben feldolgozza a felhasználói eseményeket.

// ❌ ROSSZ - Egy hosszú, blokkoló feladat
function processLargeDataset(items) {
  for (const item of items) {
    // Nehéz számítás minden elemre
    complexCalculation(item);
    updateDOM(item);
  }
}

// ✅ JÓ - scheduler.yield() használata (modern böngészők)
async function processLargeDatasetOptimized(items) {
  const BATCH_SIZE = 50;

  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    const batch = items.slice(i, i + BATCH_SIZE);

    for (const item of batch) {
      complexCalculation(item);
      updateDOM(item);
    }

    // Visszaadjuk a vezérlést a böngészőnek
    // A scheduler.yield() a feladat elején folytatja, nem a sor végén
    if (typeof scheduler !== 'undefined' && scheduler.yield) {
      await scheduler.yield();
    } else {
      // Fallback régebbi böngészőkhöz
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }
}

A Scheduler API és feladatprioritizálás

A modern böngészők Scheduler API-ja három prioritási szintet kínál, és érdemes kihasználni mindet:

// Scheduler API - feladatok priorizálása
// 1. user-blocking: Kritikus interakciók (pl. input kezelés)
scheduler.postTask(() => {
  handleUserClick();
}, { priority: 'user-blocking' });

// 2. user-visible: Fontos, de nem azonnali (pl. lista frissítés)
scheduler.postTask(() => {
  updateProductList(newData);
}, { priority: 'user-visible' });

// 3. background: Alacsony prioritás (pl. analitika, prefetch)
scheduler.postTask(() => {
  sendAnalyticsEvent('page_view', pageData);
  prefetchNextPage();
}, { priority: 'background' });

// requestIdleCallback - a böngésző üresjárataiban futó feladatok
requestIdleCallback((deadline) => {
  // deadline.timeRemaining() megmondja, mennyi időnk van
  while (deadline.timeRemaining() > 5 && tasksQueue.length > 0) {
    const task = tasksQueue.shift();
    task.execute();
  }

  // Ha maradt feladat, ütemezzük a következő üresjáratra
  if (tasksQueue.length > 0) {
    requestIdleCallback(processRemainingTasks);
  }
}, { timeout: 2000 }); // Maximum 2 mp várakozás

Web Worker-ek: nehéz számítások a háttérszálon

A Web Worker-ek lehetővé teszik, hogy a CPU-intenzív feladatokat a fő száltól teljesen független háttérszálakon futtasd. Ideális megoldás adatfeldolgozáshoz, képmanipulációhoz, keresési indexeléshez — gyakorlatilag mindenhez, ami sok számolással jár, de nincs szüksége DOM-hozzáférésre.

// worker.js - Háttérszálon futó nehéz számítás
self.addEventListener('message', (event) => {
  const { type, data } = event.data;

  switch (type) {
    case 'PROCESS_DATA': {
      const result = heavyDataProcessing(data);
      self.postMessage({ type: 'RESULT', result });
      break;
    }
    case 'SEARCH': {
      const matches = fuzzySearch(data.query, data.items);
      self.postMessage({ type: 'SEARCH_RESULT', matches });
      break;
    }
  }
});

function heavyDataProcessing(rawData) {
  // Ez a számítás nem blokkolja a fő szálat
  return rawData
    .filter(item => item.isValid)
    .map(item => transform(item))
    .sort((a, b) => b.score - a.score);
}

// main.js - A fő szálról indítjuk a worker-t
const worker = new Worker(new URL('./worker.js', import.meta.url), {
  type: 'module'
});

worker.addEventListener('message', (event) => {
  const { type, result, matches } = event.data;
  if (type === 'RESULT') {
    renderResults(result); // A DOM frissítés már a fő szálon
  }
  if (type === 'SEARCH_RESULT') {
    displaySearchResults(matches);
  }
});

// Nehéz feldolgozás indítása anélkül, hogy blokkolnánk az UI-t
worker.postMessage({ type: 'PROCESS_DATA', data: largeDataset });

Modulok előtöltése és a hálózati vízesés-effektus elkerülése

A code splitting egyik nem kívánt mellékhatása a hálózati vízesés-effektus (waterfall): a böngésző csak akkor fedezi fel a szükséges chunk-ot, amikor az importáló modul végrehajtódik. Ez kaszkádszerű letöltési láncot eredményezhet, ami pont az ellenkezője annak, amit akartunk.

Szerencsére van rá megoldás.

modulepreload: natív modul előtöltés

A <link rel="modulepreload"> deklaratív módon jelzi a böngészőnek, hogy egy ES modult előre le kell tölteni, elemezni és készen kell tartani. Ez a hagyományos preload-nál hatékonyabb, mert nemcsak letölti, hanem előre feldolgozza a modult és annak függőségeit is.

<!DOCTYPE html>
<html lang="hu">
<head>
  <meta charset="UTF-8">

  <!-- Fő alkalmazás modul -->
  <script type="module" src="/js/app.js"></script>

  <!-- Előtölti a kritikus függőségeket párhuzamosan -->
  <link rel="modulepreload" href="/js/framework.js">
  <link rel="modulepreload" href="/js/router.js">
  <link rel="modulepreload" href="/js/home-page.js">

  <!-- A kevésbé sürgős chunk-ok normál prefetch-csel -->
  <link rel="prefetch" href="/js/analytics-page.js">
  <link rel="prefetch" href="/js/settings-page.js">
</head>
<body>
  <!-- ... -->
</body>
</html>

Intelligens prefetching: az előrelátó betöltés

Az igazán ügyes megoldás az intelligens prefetching, amely a felhasználó viselkedése alapján próbálja megjósolni, melyik chunk-ra lesz szüksége. Meglepően jól működik egyszerű heurisztikákkal is — például hover események figyelésével.

// Intelligens prefetching - hover alapján
function setupSmartPrefetching() {
  const prefetchedUrls = new Set();

  document.addEventListener('mouseover', (event) => {
    const link = event.target.closest('a[href]');
    if (!link) return;

    const href = link.getAttribute('href');
    if (prefetchedUrls.has(href)) return;

    // Csak belső linkekre prefetch-elünk
    if (href.startsWith('/') || href.startsWith(window.location.origin)) {
      prefetchedUrls.add(href);

      const prefetchLink = document.createElement('link');
      prefetchLink.rel = 'prefetch';
      prefetchLink.href = href;
      document.head.appendChild(prefetchLink);
    }
  });
}

// Intersection Observer alapú prefetching
function setupViewportPrefetching() {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const link = entry.target;
        const href = link.getAttribute('href');

        // Prefetch a chunk-ot, amikor a link a viewport közelébe ér
        const prefetchLink = document.createElement('link');
        prefetchLink.rel = 'prefetch';
        prefetchLink.href = href;
        document.head.appendChild(prefetchLink);

        observer.unobserve(link);
      }
    });
  }, { rootMargin: '200px' }); // 200px-el a viewport előtt indítjuk

  document.querySelectorAll('a[data-prefetch]').forEach(link => {
    observer.observe(link);
  });
}

Gyakorlati ellenőrzőlista: a JavaScript bundle optimalizálás lépései

Szeretem az ellenőrzőlistákat, mert segítenek, hogy ne felejtsünk el semmit. Az alábbi lista prioritás szerint van rendezve — a legnagyobb hatású technikák vannak elöl.

1. Mérd fel a kiindulási állapotot

  • Futtass Lighthouse auditot és jegyezd fel a „Reduce unused JavaScript" értéket
  • Használj bundle analyzert (webpack-bundle-analyzer vagy rollup-plugin-visualizer)
  • Ellenőrizd a Coverage eszközt a Chrome DevTools-ban a kihasználatlan kód arányáért
  • Állíts be teljesítmény költségvetést (performance budget) a build eszközödben
// webpack.config.js - Performance budget
module.exports = {
  performance: {
    maxEntrypointSize: 250000, // 250 KB
    maxAssetSize: 200000,      // 200 KB
    hints: 'error',            // 'warning' helyett 'error': a build elbukik
  },
};

// bundlesize csomag használata CI/CD-ben
// package.json
{
  "bundlesize": [
    {
      "path": "./dist/js/main.*.js",
      "maxSize": "150 kB",
      "compression": "gzip"
    },
    {
      "path": "./dist/js/vendor.*.js",
      "maxSize": "100 kB",
      "compression": "gzip"
    }
  ]
}

2. Alkalmazd a code splitting-et

  • Valósítsd meg az útvonal-alapú code splitting-et
  • Azonosítsd és lazy-load-old a nehéz komponenseket (diagramok, editorok, térképek)
  • Válaszd külön a vendor kódot az alkalmazáskódtól
  • Csoportosítsd a gyakran együtt változó kódot közös chunk-okba

3. Optimalizáld a tree shaking-et

  • Használj ES modul szintaxist mindenhol (import/export)
  • Állítsd be a sideEffects mezőt a package.json-ban
  • Kerüld a barrel export fájlokat, vagy használj közvetlen importokat
  • Ellenőrizd, hogy a függőségeid támogatják-e a tree shaking-et

4. Csökkentsd a harmadik féltől származó kód mennyiségét

  • Auditáld a node_modules méretét — van-e felesleges vagy kiváltható függőség?
  • Cseréld le a nehéz könyvtárakat könnyebb alternatívákra (moment.js → date-fns vagy Intl API, lodash → natív ES metódusok)
  • Fontold meg az import() használatát ritkán használt könyvtárakhoz

5. Optimalizáld a futásidejű teljesítményt

  • Bontsd szét a Long Task-okat scheduler.yield() vagy setTimeout használatával
  • Használj Web Worker-eket CPU-intenzív feladatokhoz
  • Alkalmazd a requestIdleCallback-ot alacsony prioritású feladatokhoz
  • Használj modulepreload-ot a kritikus modulok előtöltéséhez

Valós esettanulmány: egy e-kereskedelmi alkalmazás optimalizálása

Hogy mindez ne maradjon puszta elmélet, nézzünk egy valós szcenáriót. Képzeljünk el egy közepes méretű e-kereskedelmi React alkalmazást a következő problémákkal:

  • Kezdeti bundle méret: 1,8 MB (gzip előtt), 580 KB (gzip után)
  • LCP: 4,2 másodperc mobilon
  • INP: 340 ms (messze a 200 ms-os küszöbérték felett)
  • Lighthouse Performance pontszám: 42/100

Nem egy szép látvány, ugye?

1. lépés: Bundle analízis

A webpack-bundle-analyzer futtatása után kiderült, hogy a bundle 40%-át három könyvtár teszi ki: moment.js (72 KB gzip), lodash (71 KB gzip), és egy teljes chart.js (60 KB gzip) — amit ráadásul csak egyetlen admin oldalon használnak.

2. lépés: Azonnali nyereségek (quick wins)

// ELŐTTE: Teljes lodash importálása
import _ from 'lodash';
const result = _.debounce(search, 300);

// UTÁNA: Csak a szükséges függvény
import debounce from 'lodash/debounce';
const result = debounce(search, 300);

// ELŐTTE: moment.js (72 KB gzip)
import moment from 'moment';
import 'moment/locale/hu';
const formatted = moment(date).locale('hu').format('YYYY. MMMM DD.');

// UTÁNA: natív Intl API (0 KB - beépített!)
const formatted = new Intl.DateTimeFormat('hu-HU', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
}).format(new Date(date));

3. lépés: Útvonal-alapú code splitting bevezetése

Az adminisztrációs oldal, a kosár, a checkout és a felhasználói profil oldalakat lazy-load-dal töltjük be. A chart.js ezáltal automatikusan elkülönül, hiszen csak az admin oldalon van rá szükség.

4. lépés: A termékkereső INP optimalizálása

A termékkereső szűrőjét Web Worker-be költöztettük, a lista renderelést pedig scheduler.yield()-dal bontottuk szét. Ez önmagában 200 ms-ot javított az INP-n.

Eredmények

  • Kezdeti bundle méret: 580 KB → 185 KB gzip (68%-os csökkenés)
  • LCP: 4,2 s → 1,8 s mobilon
  • INP: 340 ms → 120 ms
  • Lighthouse Performance: 42 → 89 pont

A legnagyobb hatású változtatás? A moment.js eltávolítása és az Intl API használata. Egyetlen csere, nulla új függőség, és 72 KB-tal kisebb bundle. Ez jól mutatja, hogy az optimalizálás nem mindig komplex refaktorálást igényel — néha a legegyszerűbb lépések hozzák a legnagyobb eredményt.

A jövő: Islands Architecture és szelektív hidratálás

A hagyományos SPA megközelítés legnagyobb problémája, hogy a teljes oldalt hidratálnia kell (hydration) — vagyis a szerveren renderelt HTML-hez csatolni kell a JavaScript eseménykezelőket és állapotot. Statikus tartalmú oldalakon ez rengeteg felesleges JavaScript feldolgozást jelent.

Az Islands Architecture (sziget-architektúra) radikálisan más megközelítést kínál: csak az interaktív „szigeteket" hidratálja, a statikus tartalom pedig tiszta HTML marad. Az eredmény? Drámaian kevesebb JavaScript és jobb INP.

Az Astro, a Fresh (Deno) és a Qwik keretrendszerek már natívan támogatják ezt a mintát. A 2026-os trendek szerint egyre több keretrendszer mozdul ebbe az irányba — az Angular és a Next.js is kísérletezik a részleges hidratálás különböző formáival.

<!-- Astro példa: csak az interaktív komponensek kapnak JavaScript-et -->
---
import Header from '../components/Header.astro';    // Nulla JS - tiszta HTML
import Footer from '../components/Footer.astro';    // Nulla JS - tiszta HTML
import SearchBar from '../components/SearchBar.tsx'; // React komponens - JS szükséges
import ProductGrid from '../components/ProductGrid.astro'; // Nulla JS
---

<html lang="hu">
  <body>
    <Header />

    <!-- Csak ez a "sziget" kap JavaScript-et -->
    <!-- client:visible = akkor hidratál, amikor a viewport-ba kerül -->
    <SearchBar client:visible />

    <ProductGrid products={products} />
    <Footer />
  </body>
</html>

Összefoglalás

A JavaScript bundle optimalizálás nem egyszeri feladat, hanem folyamatos gyakorlat. A HTTP Archive adatai évről évre azt mutatják, hogy az oldalak átlagos mérete nő — 2026-ra a medián oldal mérete elérte a 2,9 MB-ot desktopra. Ez a trend csak akkor fordítható meg, ha tudatosan és rendszeresen foglalkozol a bundle méret menedzsmentjével.

A legfontosabb tanulságok:

  • Mérj, mielőtt optimalizálsz: Bundle analyzer, Lighthouse és Coverage eszköz nélkül vakrepülés az egész.
  • A code splitting az alap: Útvonal-alapú és komponens-szintű kódszétválasztással a felhasználó csak azt tölti le, amire szüksége van.
  • A tree shaking nem varázslat: ES modul szintaxis és helyes sideEffects konfiguráció nélkül nem fog működni rendesen.
  • A futásidejű teljesítmény legalább annyira fontos: scheduler.yield(), Web Worker-ek és a Scheduler API segítségével a fő szál tehermentesíthető.
  • A modern bundler-ek sokat segítenek: A Vite 8/Rolldown kombináció natív sebességű build-et és kiváló tree shaking-et ad egyben.
  • A jövő az Islands Architecture felé mutat: A szelektív hidratálás drasztikusan csökkenti a szükséges JavaScript mennyiségét.

Állíts be teljesítmény költségvetést, integráld a bundle méret ellenőrzést a CI/CD pipeline-odba, és rendszeresen auditáld a függőségeidet. A felhasználóid — és a keresőoptimalizálás — hálás lesz érte.

A Szerzőről Editorial Team

Our team of expert writers and editors.