Dlaczego rozmiar pakietu JavaScriptu w Next.js ma znaczenie?
Next.js automatycznie dzieli kod na osobne pakiety per route (), ale nie zapewni to ochrony przed importowaniem całego lodash zamiast jednej funkcji, ciężkimi bibliotekami UI w każdym Client Component i zależnościami, które powinny zostać na serwerze.
Zacznij od granicy 'use client'
Dyrektywa 'use client' wyznacza granicę między grafem modułów serwera i klienta. Wszystko, co importuje Client Component, może trafić do paczki klienta. Dlatego nie oznaczaj nią całego layoutu lub strony tylko dlatego, że zawierają jeden interaktywny element. Zostaw stronę jako Server Component, a 'use client' umieść w małym komponencie formularza, wyszukiwarki czy przycisku.
Krok 1: zmierz pakiet JavaScriptu wbudowanym analizatorem Next.js
W Next.js 16.1+ analizator Turbopacka pozwala filtrować moduły po trasie i środowisku (klient lub serwer), a także prześledzić pełny łańcuch importów. To najlepszy punkt startu w projekcie korzystającym z domyślnego bundlera:
Do porównania raportu przed i po optymalizacji zapisz go na dysku:
Raport trafi do .next/diagnostics/analyze. Nie powstaje przy tym artefakt produkcyjnego buildu, więc standardowy npm run build nadal uruchom osobno przed wdrożeniem.
Alternatywa: @next/bundle-analyzer dla Webpacka
Jeśli projekt jest nadal budowany Webpackiem albo chcesz porównać jego raport, użyj wtyczki @next/bundle-analyzer:
Wtyczka otworzy osobne raporty dla pakietów klienta, serwera i edge runtime. Nie traktuj jednak wyniku Webpacka jako dokładnego odpowiednika buildu Turbopacka: do optymalizacji produkcyjnej analizuj przede wszystkim bundler używany przez projekt.
Czego szukać w analizie paczek kodu Next.js?
Duże biblioteki w pakiecie klienta
Typowi winowajcy:
| Biblioteka lub typ kodu | Co sprawdzić w raporcie | Typowa decyzja |
|---|---|---|
moment.js | Czy potrzebujesz pełnej obsługi dat i lokalizacji? | Użyj mniejszej biblioteki lub tylko potrzebnych funkcji. |
lodash | Czy importujesz cały pakiet? | Importuj pojedynczy moduł, użyj lodash-es albo natywnego API. |
framer-motion | Czy animacja jest potrzebna przy pierwszym renderze? | Użyj CSS lub załaduj komponent warunkowo. |
| Biblioteka wykresów | Czy wykres pojawia się na każdej trasie? | Ładuj go dynamicznie tylko na trasie lub po interakcji. |
| Biblioteka komponentów | Czy importy są zoptymalizowane i które komponenty są klientowe? | Korzystaj z nazwanych importów i sprawdź wynik w mapie drzewa. |
Nie zakładaj, że zamiana jednej biblioteki na drugą automatycznie zmniejszy wynik. Rozmiar zależy od wersji, sposobu importu, użytych funkcji i pozostałych zależności. Zawsze porównaj raport przed i po zmianie.
Biblioteki serwerowe przypadkiem wrzucone do pakietu klienta
To najgroźniejszy przypadek, ponieważ jeśli w pakiecie klienta widzisz prisma, bcrypt, stripe albo @aws-sdk, kod serwerowy przedostał się do grafu klienta. Część takich importów zakończy build błędem, a inne niepotrzebnie zwiększą paczkę lub ujawnią nieprzeznaczony dla przeglądarki kod. Zwykle chodzi o jeden plik, importowany i przez Server, i przez Client Component.
Rozwiązanie: rozdziel na osobne pliki:
Lub użyj server-only package:
Krok 2: optymalizacja pakietu JavaScriptu w Next.js
Dynamiczny import i leniwe ładowanie ciężkich komponentów
dynamic() wydziela komponent do osobnego pliku JavaScriptu. Żeby odroczyć jego pobranie, renderuj go dopiero wtedy, gdy użytkownik go potrzebuje, jak w przykładzie po kliknięciu. Samo ssr: false wyłącza renderowanie na serwerze, ale nie oznacza automatycznie ładowania po interakcji.
Tree shaking: importuj tylko potrzebny kod
usuwa z pakietu kod, którego nie używasz — ale tylko wtedy, gdy importujesz w sposób, który mu na to pozwala.
Next.js ma tu wbudowanego, eksperymentalnego pomocnika: optimizePackageImports. Dla wskazanych pakietów kompilator przepisuje import z barrela na bezpośrednie importy plików, więc nazwany import z ogromnej biblioteki przestaje ryzykować wciągnięciem całości. Popularne pakiety (m.in. lucide-react, date-fns, lodash-es, @mui/icons-material) są optymalizowane automatycznie — własne dopisujesz w konfiguracji:
Barrel files we własnym kodzie: sprawdź, czy nie utrudniają tree shakingu
Barrel file — index.ts re-eksportujący wszystko z katalogu — daje krótkie importy. Przy czystych modułach ESM bundler zwykle usunie nieużywane eksporty. Sprawdź go jednak, gdy mapa drzewa pokazuje kod niepotrzebny na danej trasie: efekty uboczne, CommonJS lub nieoptymalny graf importów mogą uniemożliwić bezpieczne wycięcie modułu.
optimizePackageImports działa tylko dla pakietów z node_modules — własnych barreli nie zmienia. Reguła praktyczna: gdy ciężka zależność pojawia się na trasie, która jej nie używa, zaimportuj komponent bezpośrednio z pliku i porównaj raport. Nie zakładaj jednak, że każdy barrel jest błędem: decyduje wynik analizy, nie sam styl importu.
Jak sprawdzić rozmiar zależności przed instalacją?
Zanim pakiet trafi do package.json, sprawdź, ile waży: bundlephobia.com pokazuje rozmiar po minifikacji i gzipie, koszt per wersja i to, czy pakiet wspiera tree shaking. Jak dla mnie, najwygodniejsze jest rozszerzenie VS Code Import Cost, które wyświetla rozmiar każdego importu bezpośrednio w edytorze. Dzięki temu ciężkie zależności zauważysz już w momencie ich dodawania, zamiast wykrywać je z opóźnieniem podczas analizy drzewa paczki.
Server Components i redukcja JavaScriptu w pakiecie klienta
W ramach optymalizacji, najlepiej przenieść logikę z Client na Server Components. Własny kod Server Component i używane przez niego biblioteki nie są wysyłane jako JavaScript do hydratacji, choć odpowiedź nadal zawiera wyrenderowany HTML i dane RSC potrzebne Reactowi.
Krok 3: pilnuj budżetu rozmiaru pakietu w CI/CD
Najpierw zapisz raport dla ważnych tras i ustal wartość bazową. Budżet powinien dotyczyć konkretnego artefaktu lub grupy plików, które chcesz kontrolować, a nie przypadkowego rozmiaru całego katalogu .next.
Przykład z size-limit dla wygenerowanych plików klienta:
Dodaj ten skrypt do pipeline'u CI/CD:
size-limit zakończy zadanie błędem po przekroczeniu limitu. Jeśli potrzebujesz kontroli konkretnych tras lub różnicy względem gałęzi bazowej, użyj raportu analizatora, bundlewatch albo narzędzia dopasowanego do platformy wdrożeniowej.
Jak oceniać wynik analizy?
Nie porównuj tras tylko po jednej liczbie kilobajtów. W raporcie odpowiedz kolejno na trzy pytania:
- Czy ciężki moduł jest w paczce klienta, choć mógł pozostać na serwerze?
- Czy jest potrzebny przy pierwszym renderze, czy można załadować go po interakcji?
- Który import wprowadza go na trasę?
Po zmianie wygeneruj drugi raport i sprawdź rzeczywisty efekt w Lighthouse lub na danych RUM. Analiza pakietu pokazuje przyczynę, ale nie zastępuje pomiaru czasu ładowania, interaktywności i Core Web Vitals na urządzeniach użytkowników.
