Lighthouse CI w 2026: budżety wydajności, GitHub Actions i blokowanie regresji

Praktyczna konfiguracja Lighthouse CI 0.14 w 2026: lighthouserc.js, budżety wydajności w budget.json, integracja z GitHub Actions i realne progi dla LCP, INP i CLS, które blokują regresje w PR.

Lighthouse CI 2026: budżety i CI/CD

Ostatnia aktualizacja: 14 września 2026

Lighthouse CI (LHCI) w 2026 roku to darmowe narzędzie od zespołu Chrome, które uruchamia Lighthouse w potoku CI/CD, porównuje wyniki z budżetem wydajności i automatycznie blokuje pull request, jeśli LCP, INP lub rozmiar bundle'a przekroczą próg. W praktyce oznacza to, że każda regresja Core Web Vitals (nawet ta wprowadzona przez skrypt marketingowy dodany po cichu przez inny zespół) zatrzymuje się na CI, zanim trafi na produkcję. Szczerze, w ostatnim projekcie e-commerce ustawiliśmy LHCI głównie po to, żeby jedna źle zaimportowana biblioteka nie zabijała nam LCP na kartach produktów, i to naprawdę zadziałało.

  • Lighthouse CI 0.14 (wrzesień 2026) domyślnie używa Chrome for Testing 129 i wspiera nową metrykę INP w ramach Core Web Vitals.
  • Plik lighthouserc.js definiuje trzy sekcje: collect (jak zbierać dane), assert (progi jako budżet) i upload (gdzie wysyłać raporty).
  • Budżet w formacie budget.json (LightWallet) blokuje PR, jeśli sumaryczny JavaScript przekroczy np. 170 KB gzipped, albo obraz LCP waży więcej niż 100 KB.
  • Uruchomienie 3–5 przebiegów i użycie mediany (numberOfRuns: 5) eliminuje szum sieci; pojedynczy pomiar w CI jest po prostu niewiarygodny.
  • Integracja z GitHub Actions zajmuje jakieś 20 linii YAML, a komentarz z linkiem do raportu HTML pojawia się w PR w ciągu 2 minut.
  • LHCI Server (własny hosting) trzyma historię wyników i pozwala śledzić trendy na osi czasu. Bez niego widzisz tylko pojedynczy punkt w czasie.

Czym jest Lighthouse CI i co się zmieniło w 2026

Lighthouse CI to zestaw narzędzi wiersza poleceń (@lhci/cli) i opcjonalny serwer historii wyników. Uruchamia standardowy Lighthouse (ten sam, który widzisz w Chrome DevTools), ale w trybie headless na maszynie CI, a następnie porównuje wyniki z zestawem asercji zapisanych w repozytorium. Jeśli LCP na karcie produktu wzrośnie z 2.1 s do 2.8 s po dodaniu nowego widgetu opinii, LHCI zwraca kod błędu i blokuje merge. Proste, ale zaskakująco skuteczne.

W wydaniu 0.14 z sierpnia 2026 zespół Chrome zaktualizował domyślny binarny Chrome do Chrome for Testing 129 (poprzednio 121), co przywróciło zgodność z nową metodologią liczenia INP wprowadzoną w Chrome 128. Wprowadzono też wsparcie dla collect.settings.throttlingMethod: "devtools" jako domyślnej opcji zamiast starego simulate, co dało bardziej wiarygodne wyniki INP w środowiskach kontenerowych. Dokumentacja projektu Lighthouse CI na GitHubie opisuje pełny changelog, i warto tam zajrzeć przed aktualizacją.

W praktyce oznacza to, że starsze konfiguracje z 2024 roku mogą raportować sztucznie niskie INP. Jeśli twój lighthouserc.js nie był ruszany od dawna, wartości mogą się zmienić po aktualizacji, i to nie z powodu twojego kodu.

Instalacja i minimalna konfiguracja lighthouserc.js

Instalacja to jeden pakiet w devDependencies. Nie potrzebujesz Docker Image ani ręcznego pobierania Chrome, bo @lhci/cli pociągnie za sobą Chrome for Testing przez zależność puppeteer.

npm install --save-dev @lhci/[email protected]

W katalogu głównym projektu utwórz plik lighthouserc.js. To jest minimalna, produkcyjna konfiguracja, której używam w e-commerce dla trzech kluczowych szablonów: strona główna, lista produktów (PLP), karta produktu (PDP).

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      // Uruchom 5 przebiegów per URL i weź medianę
      numberOfRuns: 5,
      // Statyczny build lub URL preview z Vercel/Netlify
      url: [
        'https://preview.example.com/',
        'https://preview.example.com/kategoria/buty-meskie',
        'https://preview.example.com/produkt/adidas-samba-og'
      ],
      settings: {
        // devtools throttling zamiast simulate, stabilniejsze INP
        throttlingMethod: 'devtools',
        // Emulacja mobilna Moto G4, tak jak liczy CrUX
        preset: 'perf',
        // Wyłącz Storage Reset między przebiegami dla warm-cache
        skipAudits: ['uses-http2']
      }
    },
    assert: {
      preset: 'lighthouse:recommended',
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'interaction-to-next-paint': ['warn', { maxNumericValue: 200 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'total-blocking-time': ['warn', { maxNumericValue: 300 }],
        // Blokuj nowe bundle powyżej 170 KB gzip
        'resource-summary:script:size': ['error', { maxNumericValue: 175000 }],
        'unused-javascript': ['warn', { maxLength: 5 }]
      }
    },
    upload: {
      // Bezpłatny hosting HTML raportów przez zespół Chrome
      target: 'temporary-public-storage'
    }
  }
};

Trzy sekcje warto zapamiętać. collect mówi jak zbierać (URL, liczba przebiegów, throttling), assert definiuje budżet (co powoduje błąd, a co ostrzeżenie), a upload określa gdzie ląduje raport HTML. Ta ostatnia opcja to zwykle największa oszczędność czasu, bo bez uploadu zespół nie widzi, dlaczego LCP wzrosło.

Budżet wydajności: budget.json vs assertions

W LHCI masz dwa nakładające się mechanizmy budżetowania: LightWallet (plik budget.json) i assertions w lighthouserc.js. LightWallet jest starszy, prostszy, i skupiony na rozmiarach zasobów (nie na metrykach). Assertions są bardziej wyraziste i mogą sprawdzać dowolny audit z Lighthouse.

W praktyce używam obu. LightWallet do twardych limitów bajtowych, które muszą trzymać się jak beton (bo nie da się ich obejść "prawie działającą" optymalizacją), i assertions dla wszystkiego, co jest metryką (LCP, INP, CLS, TBT).

// budget.json, dołączany przez settings.budgetsPath w lighthouserc.js
[
  {
    "path": "/*",
    "resourceSizes": [
      { "resourceType": "script",     "budget": 170 },
      { "resourceType": "stylesheet", "budget": 40  },
      { "resourceType": "image",      "budget": 250 },
      { "resourceType": "font",       "budget": 90  },
      { "resourceType": "third-party","budget": 200 },
      { "resourceType": "total",      "budget": 800 }
    ],
    "resourceCounts": [
      { "resourceType": "third-party", "budget": 12 }
    ]
  },
  {
    "path": "/produkt/*",
    "resourceSizes": [
      { "resourceType": "script",     "budget": 200 },
      { "resourceType": "third-party","budget": 260 }
    ]
  }
]

Zwróć uwagę na dopasowanie per-path. W moim sklepie karta produktu ma o 30 KB wyższy budżet skryptu, bo tam siedzi widget rozmiarówki, którego nie ma na kategorii. Bez per-path budżetu musiałbym podnieść globalny limit dla całej strony, tracąc ochronę dla PLP.

Integracja z GitHub Actions krok po kroku

Poniższy workflow uruchamia Lighthouse CI na każdym PR wskazującym na main. Zakłada, że masz statyczny build w ./dist. Jeśli używasz Next.js z SSR, zamień lhci autorun na wariant z startServerCommand.

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on:
  pull_request:
    branches: [main]

jobs:
  lhci:
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'npm'
      - run: npm ci
      - run: npm run build

      - name: Uruchom Lighthouse CI
        run: npx @lhci/[email protected] autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

      - name: Prześlij raporty jako artefakt
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lighthouse-reports
          path: .lighthouseci/
          retention-days: 14

Token LHCI_GITHUB_APP_TOKEN otrzymasz instalując aplikację GitHub "Lighthouse CI" z GitHub Marketplace. Dzięki niemu LHCI dokleja komentarz z tabelką wyników pod PR, zamiast wysyłać tylko surowy status check. Zespół widzi o ile LCP wzrosło, nie tylko czerwony krzyżyk. Ta różnica w praktyce decyduje o tym, czy ludzie w ogóle czytają wyniki.

Jeśli równolegle stosujesz optymalizację bundle'a JavaScript z tree shakingiem i import maps, LHCI zauważy nawet zmianę o kilkanaście kilobajtów w chunk'ach wchodzących na ścieżkę krytyczną. Połączenie tych dwóch mechanizmów daje ochronę przed regresjami "od dołu".

Jakie progi ustawić dla Core Web Vitals

Progi w LHCI powinny odzwierciedlać terenowe cele Core Web Vitals, nie idealne wartości laboratoryjne. W 2026 zespół web.dev utrzymuje trzy oficjalne progi "dobry":

MetrykaDobryWymaga poprawySłaby
LCP≤ 2.5 s2.5–4.0 s> 4.0 s
INP≤ 200 ms200–500 ms> 500 ms
CLS≤ 0.10.1–0.25> 0.25
TTFB≤ 800 ms0.8–1.8 s> 1.8 s
FCP≤ 1.8 s1.8–3.0 s> 3.0 s

W laboratorium (LHCI) możesz i powinieneś ustawić progi ostrzejsze niż terenowe. Powód: Lighthouse mierzy w kontrolowanych warunkach, a użytkownicy siedzą za wolnym LTE i starym Androidem. Osobiście na LCP ustawiam error na 2000 ms w LHCI, żeby zostawić 500 ms bufora na rzeczywistą sieć. Ta rezerwa niejednokrotnie mnie uratowała.

Jeśli chcesz zrozumieć, dlaczego dane laboratoryjne rozjeżdżają się z terenowymi, zajrzyj do przewodnika o Real User Monitoring Core Web Vitals z web-vitals.js i CrUX API. To dla mnie obowiązkowa lektura, zanim zaczniesz debatować z zespołem nad "prawdziwymi" liczbami.

Jak walczyć z szumem: mediana, warm-up, throttling

Pojedynczy przebieg Lighthouse w CI ma odchylenie standardowe ±10–15% dla LCP i nawet ±30% dla INP. Uruchamianie jednego przebiegu w pipeline daje ci właściwie generator losowy z opinią. Trzy rzeczy, które musisz zrobić, żeby wyniki były wiarygodne:

1. numberOfRuns: 5 i mediana

LHCI automatycznie liczy medianę z numberOfRuns. Pięć przebiegów to sweet spot; trzy nadal bywają hazardem, siedem to strata minut CI. Mediana filtruje pojedyncze outliery znacznie lepiej niż średnia.

2. Throttling przez DevTools, nie CPU

Domyślny throttlingMethod: 'simulate' nakłada model matematyczny na już zebrane dane. Na wolnych runnerach GitHub Actions daje to nienaturalnie niskie INP. Ustaw throttlingMethod: 'devtools', bo wtedy Chrome faktycznie zwalnia sieć i CPU podczas nagrania.

3. Dedykowany runner lub self-hosted

Runnery ubuntu-24.04 mają dzielone CPU i wariancję ±20% w dostępnych cyklach. Jeśli twoje wyniki tańczą pomimo pięciu przebiegów, przenieś LHCI na self-hosted runner z izolowanym CPU, albo użyj ubuntu-24.04-arm (Graviton), który zachowuje się bardziej deterministycznie.

LHCI Server: historia wyników i trendy

Domyślny target: 'temporary-public-storage' hostuje raport HTML przez 7 dni na Google Cloud Storage. Świetne do debugowania konkretnego PR, ale bez historii. Żeby zobaczyć trend LCP na przestrzeni sprintów, potrzebujesz LHCI Server: samodzielna aplikacja Node.js + PostgreSQL, którą można uruchomić na Fly.io lub Cloud Run za mniej niż 5 USD miesięcznie.

# Dockerfile: LHCI Server produkcyjny
FROM node:22-alpine
WORKDIR /usr/src/lhci
RUN npm install -g @lhci/[email protected] pg
ENV LHCI_STORAGE__SQL_DIALECT=postgres
ENV LHCI_STORAGE__SQL_CONNECTION_URL=$DATABASE_URL
EXPOSE 9001
CMD ["lhci", "server"]

Po deploymencie zmieniasz upload w lighthouserc.js:

upload: {
  target: 'lhci',
  serverBaseUrl: 'https://lhci.example.com',
  token: process.env.LHCI_BUILD_TOKEN
}

Serwer daje trzy rzeczy, których nie ma temporary-public-storage: wykresy trendu per URL, porównanie build-do-build w UI, i alerty, gdy mediana z ostatnich N przebiegów wyjdzie poza budżet. To ostatnie jest ważne dla ekipy, która nie mergeuje codziennie, bo regresja z zeszłego tygodnia nie zniknie tylko dlatego, że nowy PR akurat przeszedł.

Lighthouse CI vs WebPageTest w CI

Oba narzędzia mają swoje miejsce, ale różnią się filozofią. Lighthouse CI jest darmowy, uruchamia jeden headless Chrome i skaluje się liniowo z liczbą runnerów. WebPageTest ma prywatne agenty na fizycznych urządzeniach (nawet konkretne modele Androida), obsługuje wiele lokalizacji, ale wymaga płatnej subskrypcji lub własnej infrastruktury.

CechaLighthouse CIWebPageTest
KosztDarmowe (open source)Płatne SaaS lub self-hosted
Środowisko pomiaruHeadless Chrome, CPU/network throttlingFizyczne urządzenia, prawdziwa sieć
LokalizacjeJedna (runner CI)Wiele globalnych (na płatnym planie)
Integracja z PRNatywna, GitHub AppWymaga własnego skryptu
BudżetyWbudowane (assertions + budget.json)Przez API/skrypty własne
Trendy historyczneLHCI ServerWbudowane
Testy wielokrotne / medianaWbudowane (numberOfRuns)Wbudowane

Z mojego doświadczenia LHCI wygrywa w codziennym CI (szybko, tanio, gate na PR), a WebPageTest lądował u nas raz w tygodniu z WebPageTest API na dedykowanym agencie w naszym głównym rynku (Warszawa), do porównania rzeczywistej sieci z laboratoryjnym pomiarem z LHCI.

Typowe błędy w konfiguracji i jak je naprawić

PR blokowany przez losowy szum, nie regresję

Objaw: co trzeci PR failuje na LCP, ale nikt nie widzi w kodzie zmiany. Przyczyna: numberOfRuns: 1 lub 2. Podnieś do 5, użyj assertMatrix zamiast płaskiego assertions, żeby móc dawać różne tolerancje per URL.

INP zawsze 0 albo nieprawdziwe

Objaw: INP w raporcie wynosi 0 lub 16 ms mimo wolnej strony. Przyczyna: Lighthouse nie ma interakcji użytkownika w headless. Rozwiązanie: użyj puppeteerScript w collect.puppeteerScript, który klika kluczowy przycisk (np. "dodaj do koszyka") i wtedy Lighthouse zmierzy prawdziwe INP.

// scripts/lhci-interact.js
module.exports = async (browser, context) => {
  const page = await browser.newPage();
  await page.goto(context.url, { waitUntil: 'networkidle0' });
  await page.click('[data-testid="add-to-cart"]');
  await page.waitForSelector('.cart-drawer', { timeout: 5000 });
};

Budżet działa lokalnie, w CI failuje

Objaw: npm run lhci lokalnie zielony, w GitHub Actions czerwony na tym samym commicie. Przyczyna: różnica w wersji Chrome lub locale (Chrome for Testing w CI ładuje inne fonty systemowe). Fix: zapnij wersję @lhci/[email protected] zamiast @0.14.x, żeby lokalna i CI używały identycznej Chromium.

Za dużo szumu z third-party

Objaw: budżet third-party latającego w górę i w dół po 100 KB. Przyczyna: Google Tag Manager ładuje różne skrypty w zależności od A/B testu. Rozwiązanie: uruchom LHCI z settings.blockedUrlPatterns blokującym GTM w środowisku preview, lub użyj Partytown zgodnie ze wskazówkami z artykułu o optymalizacji skryptów zewnętrznych z Partytown.

Najczęściej zadawane pytania

Czym różni się Lighthouse od Lighthouse CI?

Lighthouse to samo narzędzie audytujące (wbudowane w Chrome DevTools i dostępne jako CLI). Lighthouse CI (@lhci/cli) to nakładka, która uruchamia Lighthouse w pipeline CI, agreguje wyniki, porównuje z budżetem i integruje się z GitHub/GitLab poprzez statusy i komentarze pod PR.

Ile przebiegów Lighthouse CI potrzeba, żeby wyniki były wiarygodne?

Pięć przebiegów (numberOfRuns: 5) i użycie mediany to praktyczne minimum. Trzy przebiegi bywają wystarczające dla statycznych stron, ale dla dynamicznego e-commerce z third-party skryptami wariancja LCP potrafi być na tyle duża, że 3 przebiegi dają fałszywe alerty w około 15% PR-ów.

Czy Lighthouse CI działa z Next.js i SSR?

Tak. W sekcji collect ustaw startServerCommand: 'npm run start' i startServerReadyPattern: 'ready on'. LHCI uruchomi twój serwer produkcyjny lokalnie na czas testu, przeprowadzi audyt, i zamknie proces. Alternatywnie deployuj do preview URL (Vercel automatycznie generuje jeden per PR) i podaj ten URL w collect.url.

Jak zmierzyć INP w Lighthouse CI, skoro nie ma interakcji użytkownika?

Użyj skryptu Puppeteer w collect.puppeteerScript, który wykonuje realistyczne kliknięcia i przewinięcia. Lighthouse 12+ zbiera Long Animation Frames podczas tej sesji i wylicza INP na podstawie faktycznych interakcji. Bez tego INP w raporcie będzie sztucznie niskie, bo Lighthouse zmierzy jedynie idle stronę.

Czy mogę ustawić różne budżety wydajności dla różnych typów stron?

Tak. budget.json obsługuje pole path z globami. Możesz mieć jeden budżet dla /, inny dla /produkt/*, i jeszcze inny dla /blog/*. Assertions w lighthouserc.js obsługują assertMatrix, który daje per-URL progi metryk (LCP, INP, CLS).

Robin Chowdhury
O Autorze Robin Chowdhury

Frontend performance architect at a large e-commerce site. Spends his days fighting third-party scripts.