JavaScript jest dziś największym wąskim gardłem wydajnościowym w nowoczesnym webie. Choć w dyskusjach o wydajności najczęściej mówi się o obrazach, to właśnie nieoptymalny JavaScript po cichu niszczy Core Web Vitals, zużywa baterię urządzeń mobilnych i zniechęca użytkowników, zanim zdążą zobaczyć treść.
Dlaczego JavaScript jest problemem wydajnościowym?
Każdy bajt JavaScriptu musi zostać przez przeglądarkę:
pobrany, sparsowany, skompilowany i wykonany — nie tylko przesłany.
Plik JS o rozmiarze 300 KB kosztuje znacznie więcej niż obraz o tej samej wadze, ponieważ:
- obrazy są tylko dekodowane
- JavaScript aktywnie obciąża CPU
To szczególnie istotne, ponieważ:
- JS działa na main thread (głównym wątku) — tym samym, który obsługuje renderowanie i interakcje
- każde zadanie blokujące główny wątek na ponad 50 ms to tzw. Long Task, który pogarsza INP
- urządzenia mobilne mają CPU 3–5× wolniejsze niż desktop
- skrypty zewnętrzne konkurują o te same zasoby co Twój kod
Zrozumienie Main Thread
Główny wątek przeglądarki odpowiada za wszystko, co widzi użytkownik:
- parsowanie HTML
- stosowanie CSS
- wykonywanie JavaScriptu
- obsługę kliknięć
- renderowanie pikseli
Może wykonywać tylko jedno zadanie naraz.
Gdy JS wykonuje Long Task:
→ przeglądarka nie reaguje na użytkownika
→ spada INP
→ strona wydaje się „ociężała”
Pipeline wydajności wygląda tak:
- Przeglądarka pobiera HTML
- Zaczyna parsowanie
- Trafia na
<script>→ pauza parsowania HTML - Pobiera i wykonuje JS
- Wznawia parsowanie
- Renderuje stronę
Każdy blokujący skrypt w <head> bezpośrednio pogarsza LCP.
Jak mierzyć wydajność JavaScript
Chrome DevTools – Performance
Najważniejsze narzędzie do analizy:
- Long Tasks (na czerwono > 50 ms)
- Call Tree – które funkcje są najcięższe
- aktywność main thread podczas interakcji
Chrome DevTools – Coverage
Pokazuje, ile kodu JS jest faktycznie używane.
Jeśli 80% pliku nie jest wykorzystywane → ogromny potencjał optymalizacji.
Lighthouse (PageSpeed Insights)
Wskazuje m.in.:
- Reduce unused JavaScript
- Avoid long main-thread tasks
- Minify JavaScript
Bundlephobia / webpack-bundle-analyzer
Wizualizuje bundle JS jako mapę — łatwo zobaczyć najcięższe biblioteki.
Code Splitting – ładuj tylko to, co potrzebne
Najważniejsza technika optymalizacji JS.
Zamiast jednego dużego bundle:
→ dziel kod na mniejsze części
→ ładuj je tylko wtedy, gdy są potrzebne
Route-based splitting
// Źle:
import CheckoutPage from './CheckoutPage';
// Dobrze:
const CheckoutPage = () => import('./CheckoutPage');
Frameworki:
- Next.js – automatycznie
- SvelteKit – automatycznie
- Vite – dynamiczne importy
Component-level splitting
Nie ładuj ciężkich komponentów od razu:
- wykresy (np. Chart.js ~200 KB)
- edytory tekstu
- modale
Tree Shaking – usuwanie martwego kodu
Proces usuwania nieużywanego kodu przez bundler (Vite, Webpack, Rollup).
Wymaga użycia ES Modules (import/export).
Przykład:
// Źle:
import _ from 'lodash';
// Dobrze:
import debounce from 'lodash/debounce';
Dodatkowo:
- używaj lżejszych bibliotek (np. date-fns zamiast moment.js)
- usuwaj nieużywane pakiety npm
Strategia ładowania skryptów
Sposób użycia <script> wpływa bezpośrednio na wydajność:
| Typ | Zachowanie | Zastosowanie |
|---|---|---|
<script> | blokuje HTML | unikaj w <head> |
async | ładuje równolegle | analityka |
defer | po HTML | większość skryptów |
import() | na żądanie | funkcje opcjonalne |
Zasada:
Brak synchronicznych <script> w <head> (poza absolutnym minimum).
Web Workers – przenieś pracę poza main thread
Web Workers pozwalają wykonywać JS w tle, bez blokowania UI.
Idealne zastosowania:
- przetwarzanie JSON
- wyszukiwanie (np. Fuse.js)
- obróbka obrazów
- obliczenia
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataset });
// worker.js
self.onmessage = (e) => {
const result = expensiveCalculation(e.data.data);
self.postMessage({ result });
};
Biblioteka Comlink upraszcza pracę z workerami.
Scheduler API – oddawanie kontroli przeglądarce
Nowoczesne API: scheduler.yield()
Pozwala dzielić długie zadania:
async function processItems(items) {
for (const item of items) {
processItem(item);
await scheduler.yield();
}
}
Zastępuje stare setTimeout(fn, 0)
→ poprawia INP
Zarządzanie skryptami zewnętrznymi
Największy problem wydajnościowy.
Skrypty typu:
- analytics
- chat
- reklamy
mogą dodać nawet 500 KB+ JS.
Strategie:
- audytuj każdy skrypt
- ładuj po interakcji
- używaj tag managera z kontrolą
- hostuj krytyczne skrypty lokalnie
- ustal performance budget
Checklist optymalizacji JavaScript
Bundle
- code splitting
- tree shaking
- brak nieużywanych pakietów
- analiza bundle
Ładowanie
- defer/async
- lazy loading
- brak blokujących
<script>
Runtime
- brak Long Tasks > 50 ms
- Web Workers
- scheduler.yield()
- debounce/throttle
Third-party
- audyt
- lazy loading
- performance budget
Mindset wydajności JavaScript
To nie jednorazowa optymalizacja — to proces.
Najlepsze zespoły:
- mają performance budget w CI/CD
- monitorują bundle size
- analizują każdy PR
- śledzą Core Web Vitals 24/7
W 2026 roku wydajność JavaScript to nie niszowa wiedza — to podstawowa umiejętność frontend developera.
💡 Pro tip:
Zacznij od zakładki Coverage w Chrome DevTools — często 60–80% JS na stronie nie jest używane przy pierwszym ładowaniu. To czysty koszt — usuń go w pierwszej kolejności, a zobaczysz natychmiastową poprawę LCP i INP.