Przejdź do treści

Jak blokować kod psujący LCP i INP w Next.js?

Ustaw bramkę wydajnościową w Next.js. Lighthouse CI zablokuje PR psujący LCP, CLS czy TBT, zanim trafi na produkcję. Konfiguracja i progi krok po kroku.

Maciej Sala

Founder StriveLab

8 min czytaniaOpublikowano 30 maja 2026 (Aktualizacja 13 lipca 2026)

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:

Code
yarn add --dev @lhci/cli@0.15.1
Code
// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      // Trzy przebiegi audytu — tłumią naturalne wahania pomiarów w CI
      numberOfRuns: 3,
      startServerCommand: 'npm start',
      url: ['http://localhost:3000/', 'http://localhost:3000/produkty'],
    },
    assert: {
      assertions: {
        // Syntetyczne metryki ładowania — median bierze medianę każdej metryki
        'largest-contentful-paint': [
          'error',
          { maxNumericValue: 2500, aggregationMethod: 'median' },
        ],
        'cumulative-layout-shift': [
          'error',
          { maxNumericValue: 0.1, aggregationMethod: 'median' },
        ],
        // TBT jako sygnał ryzyka INP podczas ładowania, nie pomiar INP
        'total-blocking-time': [
          'error',
          { maxNumericValue: 200, aggregationMethod: 'median' },
        ],
        // Budżet zasobów — metryka stabilna, można trzymać ostro
        'resource-summary:script:size': ['error', { maxNumericValue: 300000 }],
      },
    },
    upload: {
      // Zapis lokalny; workflow doda raporty jako prywatny artefakt runu
      target: 'filesystem',
      outputDir: './lhci',
    },
  },
}

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.

Code
# .github/workflows/bramka-wydajnosciowa.yml
name: Bramka wydajnościowa
 
on:
  pull_request:
    branches: [main]
 
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: yarn
 
      - run: corepack enable
      - run: yarn install --frozen-lockfile
      - run: yarn build
 
      # Uruchamia przypiętą w yarn.lock wersję Lighthouse CI
      - run: yarn lhci autorun
 
      # Raport pozostaje artefaktem runu zamiast trafiać do publicznego storage
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: lighthouse-reports
          path: lhci
          retention-days: 7

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

  1. Wybierz świadomie localhost albo preview. next start na 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.

  2. Rozmiar bundla pilnuj także na poziomie artefaktów. Asercja resource-summary:script:size mierzy zasoby faktycznie pobrane przez badaną trasę, co jest zaletą, ale wynik zależy m.in. od cache i konfiguracji audytu. Analiza plików po next build jest 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.

  3. Prawdziwy INP zbieraj w terenie i testuj interakcje przed mergem. W Next.js możesz użyć useReportWebVitals albo biblioteki web-vitals z onINP i 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.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Czym jest bramka wydajnościowa?

Bramka wydajnościowa to mechanizm CI/CD, który egzekwuje ustalone budżety wydajnościowe. Jeśli pull request pogarsza LCP, rozmiar bundla, CLS albo TBT ponad ustalony limit, build robi się czerwony i zmiana nie przechodzi do main. Zamiast odkrywać regresję po kilku dniach w danych produkcyjnych, łapiesz ją w code review.

Czy Lighthouse CI mierzy INP?

Nie bezpośrednio — i to ważny niuans. INP to metryka terenowa, mierzona na realnych interakcjach użytkownika, a Lighthouse w labie nie ma takich interakcji. Zamiast tego używa TBT (Total Blocking Time) jako laboratoryjnego sygnału ryzyka responsywności podczas ładowania. TBT może korelować z INP, ale nie wykryje np. ciężkiego handlera uruchamianego dopiero po kliknięciu. W bramce ustawiasz próg na TBT, dodajesz osobne testy najważniejszych interakcji, a faktyczny INP monitorujesz z danych terenowych.

Jakie progi ustawić dla LCP, CLS i TBT?

Zacznij od bieżącego stanu aplikacji, nie od ideału. Jeśli LCP ma dziś 3 sekundy, próg 2,5 sekundy zablokuje każdy build. Ustaw limit trochę powyżej obecnego wyniku, żeby bramka łapała regresje. Potem zaostrzaj progi. Docelowo celujesz w dobre wartości Core Web Vitals: LCP do 2500 ms, CLS do 0,1, a dla TBT często okolice 200 ms.

Czy testy wydajności w CI nie są zbyt niestabilne?

Pomiary czasowe potrafią się wahać, bo maszyna CI dzieli zasoby z innymi zadaniami. Dlatego ważne są dwie rzeczy: uruchamiaj audyt kilka razy (numberOfRuns) i świadomie wybierz agregację — domyślne optimistic wybiera wartość najłatwiejszą do zaliczenia, median bierze medianę danej metryki, a median-run wynik z jednego reprezentatywnego przebiegu. Najbardziej rygorystyczne progi stawiaj na stabilne wielkości artefaktów. Nawet wynik kategorii Lighthouse jest metryką zmienną, więc nie traktuj go jak deterministycznego rozmiaru pliku. Metryki czasowe traktuj z nieco szerszym marginesem, żeby uniknąć fałszywych alarmów.

Czym bramka wydajnościowa różni się od zwykłego monitoringu wydajności?

Monitoring mówi Ci, że wydajność spadła i masz taką informacje po fakcie, gdy szkodliwa zmiana jest już na produkcji. Bramka wydajnościowa działa wcześniej, zablokuje tę zmianę, zanim w ogóle zostanie zmergowana. Oba mają sens i się uzupełniają — bramka chroni przed regresją na wejściu, monitoring łapie degradację wynikającą z czynników zewnętrznych, jak rosnące dane czy zmiany w treści. Ale to bramka jest tańszy, bo naprawiasz problem, gdy kosztuje jeden komentarz w PR, a nie utracony ruch.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Core Web Vitals — jak przyspieszyć stronę i poprawić pozycję w Google

Google od wielu lat podkreśla, że wydajność i doświadczenie użytkownika mają znaczenie i Core Web Vitals są częścią sygnałów page experience. Najważniejsze jest jednak to, że wreszcie mierzą coś bardzo konkretnego: czy strona błyskawicznie pokazuje treść, czy odpowiednio reaguje na interakcje i czy nie „skacze" nieprzyjemnie podczas ładowania.

Maciej Sala

Maciej Sala

Founder StriveLab

Lighthouse 100/100 w Astro — case study optymalizacji strony usługowej

Astro startuje z przewagą, której Next.js musi się dopracować: domyślnie wysyła zero JavaScriptu, więc świeży projekt często ma Lighthouse powyżej 95 bez jednej optymalizacji. Ale „prawie 100" a „100/100" dzieli kilka konkretnych decyzji o obrazach, fontach i skryptach. Pokazuję na case study strony usługowej, gdzie te punkty leżą — i czy w ogóle warto po nie sięgać.

Maciej Sala

Maciej Sala

Founder StriveLab

Pagespeed 100/100 w Next.js — case study optymalizacji strony usługowej

Mamy tu typową stronę usługową zbudowaną w Next.js, wyposażoną w Google Analytics, Google Tag Manager , prosty formularz kontaktowy, galerię zdjęć i animacje – czyli standardowy zestaw. Na początku testy PageSpeed Insights pokazały wyniki: 67/100 na urządzeniach mobilnych i 89/100 na komputerach stacjonarnych.

Maciej Sala

Maciej Sala

Founder StriveLab