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.
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/cli@0.14.x
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/cli@0.14.x 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":
| Metryka | Dobry | Wymaga poprawy | Słaby |
|---|---|---|---|
| LCP | ≤ 2.5 s | 2.5–4.0 s | > 4.0 s |
| INP | ≤ 200 ms | 200–500 ms | > 500 ms |
| CLS | ≤ 0.1 | 0.1–0.25 | > 0.25 |
| TTFB | ≤ 800 ms | 0.8–1.8 s | > 1.8 s |
| FCP | ≤ 1.8 s | 1.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/server@0.14.x 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.
| Cecha | Lighthouse CI | WebPageTest |
|---|---|---|
| Koszt | Darmowe (open source) | Płatne SaaS lub self-hosted |
| Środowisko pomiaru | Headless Chrome, CPU/network throttling | Fizyczne urządzenia, prawdziwa sieć |
| Lokalizacje | Jedna (runner CI) | Wiele globalnych (na płatnym planie) |
| Integracja z PR | Natywna, GitHub App | Wymaga własnego skryptu |
| Budżety | Wbudowane (assertions + budget.json) | Przez API/skrypty własne |
| Trendy historyczne | LHCI Server | Wbudowane |
| Testy wielokrotne / mediana | Wbudowane (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/cli@0.14.7 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).




