JavaScript jest jednym z największych wąskich gardeł wydajności współczesnych stron. Choć częściej mówi się o obrazach, nieoptymalny kod JavaScript może pogarszać Core Web Vitals, szybciej zużywać baterię telefonu i zniechęcać użytkowników, zanim zobaczą treść.
Dlaczego JavaScript jest problemem wydajnościowym?
Przeglądarka pobiera JavaScript, a następnie parsuje, kompiluje i wykonuje kod. Sam transfer pliku to tylko część kosztu.
Plik JavaScript o rozmiarze 300 KB może obciążyć urządzenie bardziej niż obraz o tej samej wadze, ponieważ:
- obrazy są tylko dekodowane
- JavaScript aktywnie obciąża CPU
Ma to szczególne znaczenie na urządzeniach z ograniczoną mocą i wolniejszym połączeniem:
- 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 obsługuje renderowanie i dużą część interakcji widocznych dla użytkownika.
- parsowanie HTML
- stosowanie CSS
- wykonywanie JavaScriptu
- obsługę kliknięć
- renderowanie pikseli
W danym momencie może wykonywać tylko jedno zadanie.
Jeśli JavaScript zajmie go długim zadaniem, przeglądarka przestaje reagować, INP się pogarsza, a interfejs sprawia wrażenie ociężałego.
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ę
Skrypt blokujący renderowanie w sekcji <head> może opóźnić LCP.
Jak mierzyć wydajność JavaScript
Chrome DevTools – Performance
Do sprawdzenia faktycznie używanego kodu JavaScript przydaje się narzędzie:
- Long Tasks (na czerwono > 50 ms)
- Call Tree – które funkcje są najcięższe
- aktywność main thread podczas interakcji
Chrome DevTools – Coverage
Coverage pokazuje, jaka część kodu została użyta. Jeśli 80% pliku pozostaje niewykorzystane, usuń niepotrzebny kod albo podziel bundle — zmniejszy to ilość JavaScriptu ładowanego na stronie.
Lighthouse (PageSpeed Insights)
Narzędzie do analizy bundla może dodatkowo wskazać:
- Reduce unused JavaScript
- Avoid long main-thread tasks
- Minify JavaScript
Bundlephobia / webpack-bundle-analyzer
Wizualizacja bundla pokazuje rozmiary bibliotek i pozwala szybko znaleźć te największe.
Code Splitting – ładuj tylko to, co potrzebne
Podział kodu na mniejsze części (code splitting) ogranicza ilość JavaScriptu pobieranego na start.
Zamiast jednego dużego pliku podziel kod na mniejsze moduły i ładuj je dopiero wtedy, gdy funkcja jest potrzebna.
Route-based splitting
// Źle:
import CheckoutPage from './CheckoutPage';
// Dobrze:
const CheckoutPage = () => import('./CheckoutPage');
W popularnych frameworkach można użyć między innymi:
- Next.js – automatycznie
- SvelteKit – automatycznie
- Vite – dynamiczne importy
Component-level splitting
Ciężkie komponenty, które nie są potrzebne od razu, można wczytać dopiero na żądanie:
- wykresy (np. Chart.js ~200 KB)
- edytory tekstu
- modale
Tree Shaking – usuwanie martwego kodu
Tree shaking usuwa nieużywany kod podczas budowania aplikacji, na przykład przez Vite, Webpack lub Rollup.
Aby bundler mógł wykryć nieużywane eksporty, korzystaj z modułów ES (import/export).
Przykład:
// Źle:
import _ from 'lodash';
// Dobrze:
import debounce from 'lodash/debounce';
Pozostałe techniki obejmują:
- używaj lżejszych bibliotek (np. date-fns zamiast moment.js)
- usuwaj nieużywane pakiety npm
Strategia ładowania skryptów
Atrybuty i sposób użycia znacznika <script> wpływają na kolejność pobierania oraz wykonania skryptów:
| 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 |
Unikaj synchronicznych skryptów w <head>, z wyjątkiem tych, które są niezbędne przed renderowaniem.
Web Workers – przenieś pracę poza main thread
Web Workers wykonują JavaScript w tle, dzięki czemu obliczenia nie blokują interfejsu.
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 komunikację z Web Workers.
Scheduler API – oddawanie kontroli przeglądarce
Interfejs scheduler.yield() jest obecnie obsługiwany przez główne przeglądarki i pozwala oddać im czas na inne zadania.
Dzięki temu długie zadania można podzielić na krótsze fragmenty:
async function processItems(items) {
for (const item of items) {
processItem(item);
await scheduler.yield();
}
}
W części przypadków scheduler.yield() może zastąpić setTimeout(fn, 0) i poprawić INP.
Zarządzanie skryptami zewnętrznymi
Skrypty firm trzecich potrafią mocno zwiększyć koszt działania strony.
Szczególnie dotyczy to:
- analytics
- chat
- reklamy
Takie skrypty mogą dodać nawet 500 KB+ JavaScriptu.
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
Optymalizację JavaScriptu trzeba powtarzać wraz ze zmianami aplikacji.
Skuteczne zespoły regularnie sprawdzają rozmiar bundla i wpływ skryptów na metryki:
- mają performance budget w CI/CD
- monitorują bundle size
- analizują każdy PR
- śledzą Core Web Vitals 24/7
Znajomość wydajności JavaScriptu przydaje się każdemu frontend developerowi, nie tylko zespołom dużych platform.
Zacznij od zakładki Coverage w Chrome DevTools. Sprawdź, czy przy pierwszej wizycie 60–80% załadowanego JavaScriptu pozostaje niewykorzystane. Jeśli tak, przeanalizuj ten kod w pierwszej kolejności — jego ograniczenie może poprawić LCP i INP.