Dlaczego monitoring wydajności Next.js to za mało
Każdy współczesny projekt potrzebuje monitoringu, czy to Vercel Analytics, Search Console czy własny RUM. Trudno obejść się bez niego i pomaga nam w zarządzaniu, w rozwijaniu projektów, ale jakkolwiek szczegółowy by nie był, ma jeden problem: pokazuje problemy, które już istnieją na produkcji. Mogą to być bardzo świeży problem, może to być bardzo stary, który od dłuższego czasu negatywnie wpływa na projekt - bywa z tym bardzo różnie.
Oczywistą konkluzją jest, by moment wykrycia błędów był cały etap wcześniej. Byśmy skutecznie mogli blokować przyczynę i nie wpuszczać błędów na produkcję. Pull request psujący wydajność po prostu nie wchodzi do main i właśnie taką logikę, którą stosuję przy testach regresji SEO w CI/CD: naprawa problemu, który kosztuje jeden komentarz w code review, jest nieporównywalnie tańsza niż naprawa tego samego problemu wycenianego w utraconym ruchu.
Łącząc jedno z drugim, pierwszą linią obrony jest blokowanie kodu powodującego regresję, zanim wyjdzie na produkcję. Drugą linię stanowi monitoring, który pozwala na wychwycenie rzeczy, nad którymi kod nie zapanuje. Dotyczy to rosnących danych, zmian w treści albo third-party, które nagle zwolniło.
Co da się mierzyć w CI dla LCP i INP w Next.js
Zanim ustawisz progi, rozdziel dwie rzeczy: i . W CI sprawdzisz tylko te pierwsze.
Lighthouse policzy syntetyczne LCP i CLS podczas kontrolowanego ładowania strony, więc możesz użyć ich w bramce. Nie są to jednak wartości równoważne 75. percentylowi danych terenowych. Laboratoryjne CLS może nie zobaczyć przesunięcia pojawiającego się dopiero po interakcji lub dłuższym czasie. Problemy zaczynają się przy , ponieważ ta metryka podsumowuje interakcje z całej wizyty, czego zwykły audyt nawigacyjny nie jest w stanie odtworzyć. W labie stosujesz , czyli Total Blocking Time — jako sygnał blokowania głównego wątku podczas ładowania. TBT może korelować z INP, ale nie jest jego pomiarem ani substytutem.
Wnioski? Bramkę ustawiasz na LCP, CLS, TBT i budżety zasobów, ale dla kluczowych operacji dodajesz osobne scenariusze przeglądarkowe, takie jak kliknięcie menu, filtrowanie listy, otwarcie modala czy wpisywanie w wyszukiwarkę. Możesz mierzyć je Lighthouse User Flows w trybie timespan albo narzędziami przeglądarkowymi opartymi o Event Timing. Prawdziwy INP zbierasz równolegle z danych terenowych, a jak chcesz wiedzieć skąd się on bierze po stronie kodu React, rozpisałem to w artykule o pięciu wzorcach, które niszczą INP.
Konfiguracja Lighthouse CI dla bramki wydajnościowej w Next.js
Sercem bramki wydajnościowej jest . Budżety i progi trzymasz w konfiguracji, a każda asercja czytelnie mówi, która metryka i przy jakiej wartości ma wywalić build.
Dodaj CLI jako przypiętą zależność deweloperską, żeby lokalny pomiar i CI używały tej samej wersji:
Trzy rzeczy są tu istotne. Po pierwsze, numberOfRuns: 3 ogranicza wpływ pojedynczego skoku, ale nie usuwa wariancji. Domyślne optimistic wybiera wartość najbardziej skłonną przejść asercję. median liczy medianę osobno dla wskazanego audytu; median-run bierze wartość z jednego przebiegu uznanego za reprezentatywny na podstawie kluczowych metryk, więc nie jest tym samym. Po drugie, wartości 2,5 s i 0,1 pochodzą z progów terenowych Core Web Vitals, lecz tutaj służą jako syntetyczne budżety w domyślnej emulacji mobilnej Lighthouse. Nie porównuj ich jeden do jednego z CrUX ani desktopowym Chrome bez throttlingu. Jeśli chcesz mierzyć desktop, dodaj settings: { preset: 'desktop' } w sekcji collect. Po trzecie, progi muszą uwzględniać bazową wariancję konkretnego runnera i strony.
Łącz limit absolutny z porównaniem do wersji bazowej
Sam limit absolutny ma martwą strefę: jeśli próg LCP wynosi 2500 ms, zmiana z 1600 do 2300 ms przejdzie, mimo dużej regresji. Z kolei samo porównanie z main może zaakceptować stale słabą stronę, o ile kolejny PR jej nie pogorszy. Najlepsza bramka łączy oba warunki, czyli maksymalny budżet, którego nie wolno przekroczyć oraz dopuszczalną zmianę względem bazowego commita lub stabilnego pomiaru main.
Lighthouse CI w trybie samych asercji dobrze obsługuje pierwszy warunek. Historię i porównania między buildami zapewnia własny LHCI server albo dodatkowy mechanizm porównujący raport PR z raportem bazowym. Nie porównuj dwóch pojedynczych przebiegów — uruchom oba warianty w możliwie tym samym środowisku i porównuj agregaty. Dla małych zmian czasowych ustaw najpierw ostrzeżenie, a twardy błąd dopiero przy regresji większej niż typowa wariancja runnera.
Audytuj reprezentatywne trasy, nie tylko stronę główną
Budżet strony marketingowej, listy produktów i panelu po zalogowaniu nie musi być identyczny. W konfiguracji wybierz po jednej lub kilku trasach dla każdego krytycznego szablonu, a różne progi ustaw przez assertMatrix. Testuj także stan z realistyczną treścią, ponieważ pusty grid nie ujawni kosztu np. 200 kart. Dla tras prywatnych LHCI obsługuje puppeteerScript przygotowujący sesję przed audytem. Pamiętaj tylko, żeby używać konta testowego i sekretów CI, a nie danych produkcyjnego użytkownika.
Jak wpiąć bramkę wydajnościową Next.js w GitHub Actions
Konfiguracja sama z siebie nic nie blokuje i musi uruchamiać się automatycznie przy każdej zmianie. Definiujemy więc workflow, który na każdym pull requeście buduje aplikację i odpala na niej Lighthouse CI.
Komenda lhci autorun zbiera audyty zgodnie z konfiguracją, sprawdza je względem zdefiniowanych asercji i kończy się błędem, jeśli którykolwiek próg zostanie przekroczony. Tym samym GitHub oznaczy sprawdzenie jako nieudane. Podobnie jak przy testach SEO, żeby bramka faktycznie blokowała merge, ustaw ten workflow jako wymagane sprawdzenie statusu w regułach ochrony gałęzi main. Dopiero wtedy zielony wynik staje się warunkiem wejścia kodu na produkcję.
Od tego momentu pull request przekraczający budżet kończy job czerwonym statusem i informacją, która asercja zawiodła. Krok upload-artifact wykona się także po błędzie i zachowa raporty HTML/JSON w artefaktach runu. To bezpieczniejsze ustawienie domyślne niż temporary-public-storage, gdzie każdy mający link może zobaczyć raport przechowywany w Google Cloud przez kilka dni. Publiczny upload ma sens tylko wtedy, gdy świadomie akceptujesz te warunki i raport nie zawiera prywatnych tras ani danych.
Trzy uzupełnienia bramki wydajnościowej specyficzne dla Next.js
-
Wybierz świadomie localhost albo preview.
next startna stałym runnerze daje lepszą powtarzalność i nadal testuje produkcyjny build, kompresję serwera Next.js oraz lokalny optimizer obrazów. Nie odtwarza jednak CDN-a, regionu, nagłówków platformy ani produkcyjnych usług zewnętrznych. Preview jest bardziej reprezentatywne dla hostingu, ale wnosi cold starty, zmienny cache, opóźnienie sieciowe, ochronę botów i ryzyko, że audyt ruszy przed zakończeniem deploymentu. Najlepsza praktyka to szybka, powtarzalna bramka na localhost oraz osobny pomiar preview dla regresji zależnych od infrastruktury — z kontrolą gotowości, uwierzytelnienia i cache. -
Rozmiar bundla pilnuj także na poziomie artefaktów. Asercja
resource-summary:script:sizemierzy zasoby faktycznie pobrane przez badaną trasę, co jest zaletą, ale wynik zależy m.in. od cache i konfiguracji audytu. Analiza plików ponext buildjest stabilniejsza, choć Next.js dzieli kod na współdzielone i hashowane chunki, więc prosty limit jednego pliku łatwo źle zinterpretować. Musisz ustalić budżet dla każdej trasy lub entrypointa, zapisuj statystyki builda i używaj@next/bundle-analyzerżeby diagnozować przyrosty. Kontroluj osobno rozmiar surowy i skompresowany oraz nie licz tego samego współdzielonego chunku wielokrotnie. -
Prawdziwy INP zbieraj w terenie i testuj interakcje przed mergem. W Next.js możesz użyć
useReportWebVitalsalbo bibliotekiweb-vitalszonINPi wysyłać pomiary do własnej analityki; alternatywą jest Vercel Speed Insights. Dane RUM grupuj co najmniej po trasie, klasie urządzenia i wersji wdrożenia, bez przesyłania danych osobowych. W CI dodaj testy najważniejszych interakcji, bo bramka oparta wyłącznie na TBT przepuści regresję w kodzie uruchamianym dopiero po kliknięciu.

