[ ENGINEERING_GUIDE ][ LIGHTHOUSE ][ WYDAJNOŚĆ ][ NEXTJS ][ CI_CD ]

Lighthouse CI — automatyczny audyt wydajności Next.js w GitHub Actions (2026)

28 maja 20269 min czytania
Autor: DevStudio.itStudio Web & AI

Jak zablokować regresje Core Web Vitals w PR-ach: budżety Lighthouse, lighthouserc.json, workflow GitHub Actions i kompromis GA4 vs Google Ads.

READ_TIME: 9 MIN_COMPLEXITY: MED_
STAMP: VERIFIED_BY_DS_

TL;DR

Lighthouse CI (LHCI) uruchamia audyt wydajności przy każdym pull requeście i przerywa merge, gdy LCP przekroczy 2,5 s, CLS 0,1 lub Performance score spadnie poniżej 0,85. Dla projektu Next.js 15 na Node 22 z buildem node scripts/build.cjs i lintem eslint to najtańsza polisa ubezpieczeniowa przed „małą zmianą w CSS”, która tydzień później psuje SEO i konwersje z Google Ads. Poniżej: gotowy lighthouserc.json, workflow GitHub Actions i świadomy tradeoff GA4 afterInteractive vs tagi Ads lazyOnload.

Dla kogo to jest

  • Zespołów Next.js / React z deployem na Vercel lub własnym hostingu
  • Stron firmowych, gdzie marketing i Ads zależą od szybkiego LCP
  • Firm bez dedykowanego QA wydajności — LHCI jest botem, który nie zapomina o mobile
  • Developerów zmęczonych ręcznym Lighthouse raz na kwartał „jak jakość przypadkiem jest OK”

Fraza (SEO)

lighthouse ci nextjs, automatyczny audyt wydajności, github actions lighthouse, core web vitals ci, lighthouserc.json, regresja wydajności pull request

Dlaczego jednorazowy audyt nie wystarczy

Każdy PR może wprowadzić:

  • nowy skrypt analityki bez strategy="lazyOnload",
  • obraz hero bez width/height → skok CLS,
  • import całej biblioteki ikon zamiast tree-shake,
  • font bez display: swap,
  • komponent kliencki cięższy niż poprzedni Server Component.

Lighthouse uruchomiony lokalnie na next dev często kłamie — inny bundling, brak optymalizacji produkcyjnej, inny cache. CI na artefakcie next build + next start zbliża wynik do tego, co widzi użytkownik i Google.

Stack referencyjny (DevStudio / Next.js 15)

Element Wartość
Framework Next.js 15.5.x
Node 22.x (engines w package.json)
Build npm run buildnode scripts/build.cjs
Lint npm run linteslint
Start produkcyjny npm run startnext start
Analityka GA4 afterInteractive, Google Ads lazyOnload

Ten podział tagów jest ważny w LHCI: PR dodający kolejny skrypt afterInteractive może przejść funkcjonalnie, a spaść Performance — dokładnie to ma łapać pipeline.

Progi, które warto egzekwować (mobile)

Metryka Próg w LHCI Dlaczego
LCP 2500 ms Hero, pierwsze wrażenie, SEO
CLS 0,1 Formularz „skacze” = mniej leadów
INP 200 ms (opcjonalnie w assert) Menu, chatbot, interakcje
Performance score 0,85 Sygnał jakości landing page (Ads, Search)

Progi są świadomie restrykcyjne dla strony marketingowej — aplikacja SaaS z dashboardem może mieć inne budżety.

Plik lighthouserc.json — działający przykład

Umieść w korzeniu repozytorium:

{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "startServerCommand": "npm run start",
      "startServerReadyPattern": "Ready",
      "startServerReadyTimeout": 120000,
      "url": [
        "http://localhost:3000/pl",
        "http://localhost:3000/en",
        "http://localhost:3000/pl/strony-www"
      ],
      "settings": {
        "preset": "desktop",
        "onlyCategories": ["performance", "accessibility", "best-practices", "seo"]
      }
    },
    "assert": {
      "preset": "lighthouse:recommended",
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.85 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "interactive": ["warn", { "maxNumericValue": 3800 }],
        "total-blocking-time": ["warn", { "maxNumericValue": 300 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Uwagi praktyczne:

  • numberOfRuns: 3 — mediana z trzech przebiegów redukuje szum LHCI.
  • URL-e wybierz krytyczne dla biznesu (home PL/EN + kluczowa podstrona usług).
  • Dla mobile zmień preset na "perf" lub dodaj osobny job z emulatedFormFactor: "mobile" w settings (osobny plik konfiguracji lub matrix w Actions).
  • upload.target: temporary-public-storage — szybkie linki do raportu w PR bez własnego serwera LHCI (na produkcję rozważ LHCI Server lub artefakty GitHub).

GitHub Actions — pełny workflow

Plik .github/workflows/lighthouse.yml:

name: Lighthouse CI

on:
  pull_request:
    branches: [main, master]
  push:
    branches: [main, master]

jobs:
  lhci:
    runs-on: ubuntu-latest
    timeout-minutes: 25

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Build
        run: npm run build
        env:
          # Uzupełnij zmienne wymagane przez build.cjs / Next (bez sekretów w logu)
          NODE_ENV: production

      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli@0.14.x
          lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

Opcjonalnie osobny job na nocnym deployu z URL preview Vercel (bliżej CDN):

      - name: Collect against Vercel Preview
        if: github.event_name == 'pull_request'
        run: lhci collect --url="${{ steps.vercel.outputs.preview_url }}/pl"

Preview lepiej odzwierciedla cache edge, ale wymaga stabilnego URL i sekretów — localhost po next build to dobry start bez dodatkowej infrastruktury.

Kolejność w pipeline — co musi być przed LHCI

  1. npm ci
  2. npm run lint — szybki fail przed droższym buildem
  3. npm run buildten sam build co produkcja (scripts/build.cjs)
  4. lhci autorun — start serwera, 3× URL, assert

Bez kroku 3 LHCI mierzy dev server — fałszywe zielone światło.

Typowe regresje wykryte w PR-ach

Skrypty marketingowe

Dodanie gtag bez strategy="lazyOnload" w next/script podbija TBT i opóźnia LCP. Na stronie referencyjnej:

  • GA4afterInteractive (nie gubić generate_lead przy szybkim submit),
  • Google Ads (AW-...)lazyOnload (ochrona wydajności).

PR, który przenosi wszystkie tagi na afterInteractive, może naprawić Ads, a zepsuć Performance — LHCI powinien to złapać; zespół marketingu musi wiedzieć o kompromisie (osobny artykuł: śledzenie konwersji Google Ads).

Obrazy i fonty

  • Hero: priority, znane wymiary, format AVIF/WebP gdzie możliwe.
  • Font: next/font lub display: swap.
  • Brak width/height na logo w stopce → CLS przy ładowaniu fontu.

JavaScript

  • Dynamic import dla chatbota i ciężkich widgetów.
  • Unikanie dużych paczek w Server Component layoutu.

Integracja z Vercel i RUM

Warstwa Co daje
LHCI w PR Syntetyka przed merge — blokuje regresję
Vercel Analytics / Speed Insights RUM prawdziwych użytkowników po deploy
Search Console Core Web Vitals Pola od Google z opóźnieniem

LHCI nie zastępuje RUM — używaj obu. LHCI to bramka jakości; RUM to prawda w terenie (słabe urządzenia, 3G, adblock).

Komentarz bota w PR

Z LHCI_GITHUB_APP_TOKEN (oficjalna aplikacja Lighthouse CI) raporty lądują w komentarzu z porównaniem PR vs base. Bez tokena nadal masz log w Actions i temporary-public-storage.

Rozszerzony assert — SEO i dostępność

Oprócz wydajności warto trzymać:

"categories:seo": ["warn", { "minScore": 0.9 }],
"categories:accessibility": ["warn", { "minScore": 0.9 }],
"categories:best-practices": ["warn", { "minScore": 0.9 }]

Na start ustaw warn, po ustabilizowaniu — error. Inaczej każdy PR z drobną regułą axe będzie czerwony, zespół wyłączy LHCI.

Koszt czasu w CI

Szacunek: 3 uruchomienia × 3 URL × ~40–60 s6–10 minut + build Next (2–8 min). Przyspieszenie:

  • cache ~/.npm i .next/cache w Actions,
  • mniej URL w collect (tylko /pl na feature branch),
  • pełny zestaw URL tylko na main.

To nadal tańsze niż tydzień spadku konwersji po regresji LCP.

FAQ

Czy Lighthouse CI zastępuje PageSpeed Insights ręcznie?

Nie — PSI nadal przydatny ad hoc. LHCI automatyzuje to samo na każdym PR. PSI na URL produkcyjnym zostaw jako kontrolę po release.

Czy mogę testować tylko desktop?

Możesz, ale Google używa mobile do CWV w Search. Minimum: jeden job mobile na main, desktop na PR jeśli czas CI boli.

Build wymaga zmiennych środowiskowych — co w CI?

Duplikuj niesekretne zmienne w env: joba; sekrety w GitHub Secrets. build.cjs często ładuje to samo co Vercel — bez tego build padnie przed LHCI.

PR dodaje GA4 na afterInteractive — czy to błąd?

Nie zawsze — to świadoma decyzja produktowa (nie gubić leadów). LHCI może pokazać regresję Performance — wtedy optymalizujesz inną część (obrazy, fonty, code-split), nie koniecznie cofasz GA4.

Czy eslint w tym samym workflow co LHCI?

Tak — szybki fail. Osobny workflow lint-only też OK; ważne, żeby build produkcyjny był bramką przed lhci autorun.

Lokalne uruchomienie przed pushem PR

Na maszynie developera (Node 22):

npm run build
npm run start
# w drugim terminalu:
npx @lhci/cli autorun

Jeśli lokalnie przechodzi, a CI pada — sprawdź różnice w zmiennych środowiskowych, wersji Node i cache. Jeśli lokalnie pada, a na Vercel jest OK — prawdopodobnie mierzysz dev zamiast production build.

Checklist wdrożenia w 1 dzień

  • lighthouserc.json w repo z progami LCP / CLS / Performance
  • Workflow z Node 22, npm ci, lint, build, lhci autorun
  • 2–3 URL krytyczne dla konwersji
  • Sekret LHCI_GITHUB_APP_TOKEN (opcjonalnie komentarze)
  • Zespół wie: GA4 wcześnie, Ads późno — nie „wszystko lazyOnload”
  • Po merge: RUM na produkcji dla weryfikacji

Przykładowy job mobile (osobny matrix)

W tym samym workflow dodaj drugi krok z mobile preset — krytyczne dla Google CWV:

      - name: Run Lighthouse CI (mobile)
        run: |
          npm install -g @lhci/cli@0.14.x
          lhci autorun --config=lighthouserc.mobile.json

Plik lighthouserc.mobile.json — kopia bazowej konfiguracji z:

"settings": {
  "preset": "perf",
  "formFactor": "mobile",
  "screenEmulation": { "mobile": true }
}

Asserty LCP/CLS zostaw te same — mobile jest surowszy, więc jeśli przechodzisz mobile w CI, desktop zwykle też jest bezpieczny.

Co robić, gdy LHCI failuje po merge

  1. Otwórz raport z temporary-public-storage lub komentarza bota.
  2. Sprawdź opportunities (obrazy, unused JS, render-blocking).
  3. Porównaj diff z base branch — który PR wprowadził regresję.
  4. Nie obniżaj progów „na stałe” bez uzasadnienia biznesowego — lepiej jednorazowy wyjątek z ticketem niż permanentne minScore: 0.7.

Typowy win: przeniesienie skryptu do lazyOnload, priority na LCP image, dynamic(() => import(...)) dla chatbota.

Powiązanie z Google Ads i jakością landing page

Google ocenia doświadczenie strony docelowej m.in. przez szybkość i stabilność layoutu. Regresja LCP w PR, który przechodzi merge, może podnieść CPC i obniżyć Quality Score — koszt widoczny w Ads, nie tylko w Lighthouse. Dlatego ten sam zespół, który dodaje tagi AW-, powinien widzieć czerwony LHCI zanim kampania zacznie uczyć się na wolniejszej stronie.

scripts/build.cjs — dlaczego nie surowy next build

W projekcie produkcyjnym build często opakowuje walidację env, Prisma, kopie assetów lub kroki przed next build. LHCI musi używać identycznej komendy co Vercel (npm run build), inaczej mierzysz inny bundle niż użytkownik. Jeśli build pada w CI z powodu brakującego DATABASE_URL, ustaw mock lub skip DB tylko w jobie LHCI — ale dokumentuj to w README dla zespołu.

Podsumowanie

Lighthouse CI na Next.js 15 to reguła gry: lint → build produkcyjny → audyt → assert. Progi LCP ≤ 2,5 s, CLS ≤ 0,1, Performance ≥ 0,85 chronią SEO i jakość landing page pod Ads. Pamiętaj o tradeoffie analityki: GA4 afterInteractive vs Google Ads lazyOnload — pipeline ma pokazywać koszt PR-ów, które ten balans psują. Syntetyka w CI + RUM po deploy = pełny obraz. Node 22, eslint i scripts/build.cjs to ten sam łańcuch jakości, który widzi produkcja.

Chcesz pipeline wydajności u siebie?

O autorze

Budujemy szybkie strony WWW, aplikacje web/mobile, chatboty AI i hosting — z naciskiem na SEO i konwersję.

Przydatne linki

Od teorii do produkcji — Branchly, hosting, opieka i realizacje.

PODOBA CI SIĘ NASZA ARCHITEKTURA MYŚLENIA? ZBUDUJMY COŚ RAZEM.

[ ROZPOCZNIJ_KONFIGURACJĘ_PROJEKTU ]