Web Performance i Core Web Vitals - Pytania Rekrutacyjne [2026]

Sławomir Plamowski 17 min czytania
cls core-web-vitals frontend inp lcp lighthouse pytania-rekrutacyjne web-performance

Wydajność stron to obszar, który wyróżnia seniorów od juniorów. Na rozmowach rekrutacyjnych pytania o Core Web Vitals, optymalizację i narzędzia pojawiają się coraz częściej - szczególnie w firmach dbających o UX i SEO. Ten przewodnik zawiera 45 pytań z odpowiedziami - od podstaw po zaawansowane techniki optymalizacji.

Spis treści


Dlaczego wydajność jest ważna

Jaki jest wpływ wydajności na biznes i UX?

Odpowiedź w 30 sekund: Każda sekunda opóźnienia = spadek konwersji o 7%. Google używa Core Web Vitals jako czynnika rankingowego. Wolne strony = wyższy bounce rate, mniej zakupów, gorsze SEO. Amazon: 100ms opóźnienia = 1% spadek sprzedaży.

Odpowiedź w 2 minuty:

Wydajność ma bezpośredni wpływ na biznesowe wskaźniki konwersji i zaangażowania użytkowników. Poniższa tabela pokazuje korelację między czasem ładowania a bounce rate oraz konwersją:

Wpływ wydajności na metryki biznesowe:

┌────────────────────────────────────────────────────────────┐
│  Czas ładowania    │  Bounce Rate  │  Konwersja           │
├────────────────────┼───────────────┼──────────────────────┤
│  1-2 sekundy       │  ~9%          │  Baseline            │
│  3 sekundy         │  ~32%         │  -7%                 │
│  5 sekund          │  ~90%         │  -20%                │
│  10+ sekund        │  ~123%        │  Katastrofa          │
└────────────────────────────────────────────────────────────┘

Case studies:

  • Pinterest: -40% czasu ładowania → +15% rejestracji
  • BBC: +1s ładowania → -10% użytkowników
  • Walmart: -1s ładowania → +2% konwersji
  • Google: +0.5s → -20% wyszukiwań

Core Web Vitals i SEO (od 2021):

Google Page Experience Signals:
├── Core Web Vitals (LCP, INP, CLS)
├── Mobile-friendly
├── HTTPS
├── No intrusive interstitials
└── Safe browsing

Czym jest Critical Rendering Path?

Odpowiedź w 30 sekund: Critical Rendering Path to sekwencja kroków przeglądarki do pierwszego renderowania: HTML → DOM → CSS → CSSOM → Render Tree → Layout → Paint. Optymalizacja polega na minimalizacji render-blocking resources i Critical CSS inline.

Odpowiedź w 2 minuty:

Critical Rendering Path składa się z kilku sekwencyjnych kroków, które przeglądarka wykonuje aby wyrenderować stronę. Poniższy diagram przedstawia pełny proces od parsowania HTML do wyświetlenia pikseli:

Critical Rendering Path:

HTML ──────► DOM Tree
               │
               ▼
CSS ───────► CSSOM Tree ──┐
                          │
               ┌──────────┘
               ▼
         Render Tree
               │
               ▼
           Layout (Reflow)
               │
               ▼
           Paint
               │
               ▼
          Composite
               │
               ▼
         🖥️ Pixels na ekranie

Render-blocking resources:

<!-- ❌ Blokuje rendering -->
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>

<!-- ✅ Nie blokuje -->
<link rel="stylesheet" href="print.css" media="print">
<script src="app.js" defer></script>
<script src="analytics.js" async></script>

Optymalizacja Critical Path:

<!-- 1. Inline Critical CSS -->
<style>
  /* Only above-the-fold styles */
  .header { ... }
  .hero { ... }
</style>

<!-- 2. Defer non-critical CSS -->
<link rel="preload" href="styles.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">

<!-- 3. Preconnect do external origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">

<!-- 4. Defer/async scripts -->
<script src="app.js" defer></script>

Core Web Vitals

Czym są Core Web Vitals i jakie są ich wartości docelowe?

Odpowiedź w 30 sekund: Core Web Vitals to 3 metryki Google mierzące UX: LCP (szybkość ładowania) < 2.5s, INP (responsywność) < 200ms, CLS (stabilność) < 0.1. Od marca 2024 INP zastąpił FID. Wpływają na ranking w Google.

Odpowiedź w 2 minuty:

Core Web Vitals składają się z trzech kluczowych metryk, które Google uznaje za fundamentalne dla użyteczności stron. Poniższy diagram przedstawia wszystkie trzy metryki wraz z progami dla dobrych, średnich i złych wyników:

CORE WEB VITALS (2024+)

┌─────────────────────────────────────────────────────────────────┐
│                                                                  │
│   LCP (Largest Contentful Paint)                                │
│   "Jak szybko widzę główną treść?"                              │
│   ┌────────┬────────────┬────────────┐                          │
│   │ Good   │ Needs Work │ Poor       │                          │
│   │ <2.5s  │ 2.5s-4s    │ >4s        │                          │
│   └────────┴────────────┴────────────┘                          │
│                                                                  │
│   INP (Interaction to Next Paint) ← Zastąpił FID               │
│   "Jak szybko strona reaguje na moje akcje?"                    │
│   ┌────────┬────────────┬────────────┐                          │
│   │ Good   │ Needs Work │ Poor       │                          │
│   │ <200ms │ 200-500ms  │ >500ms     │                          │
│   └────────┴────────────┴────────────┘                          │
│                                                                  │
│   CLS (Cumulative Layout Shift)                                 │
│   "Czy coś się przesunęło niespodziewanie?"                     │
│   ┌────────┬────────────┬────────────┐                          │
│   │ Good   │ Needs Work │ Poor       │                          │
│   │ <0.1   │ 0.1-0.25   │ >0.25      │                          │
│   └────────┴────────────┴────────────┘                          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Jak mierzyć w kodzie:

import { onLCP, onINP, onCLS } from 'web-vitals'

onLCP(metric => {
  console.log('LCP:', metric.value)
  // Wyślij do analytics
})

onINP(metric => {
  console.log('INP:', metric.value)
})

onCLS(metric => {
  console.log('CLS:', metric.value)
})

Jak poprawić Largest Contentful Paint (LCP)?

Odpowiedź w 30 sekund: LCP mierzy czas do wyrenderowania największego elementu (obraz, tekst, video). Optymalizuj: preload głównego obrazu, szybki TTFB, usuń render-blocking CSS/JS, użyj CDN, lazy load tylko poniżej fold.

Odpowiedź w 2 minuty:

Co wpływa na LCP:

LCP = TTFB + Resource Load Time + Render Time

1. TTFB (Time to First Byte)
   └── Serwer, DNS, SSL, redirect

2. Resource Load Time
   └── Obrazy, fonty, CSS

3. Render Time
   └── JavaScript, CSS parsing

Optymalizacje:

<!-- 1. Preload LCP image -->
<link rel="preload" as="image" href="hero.webp"
      imagesrcset="hero-400.webp 400w, hero-800.webp 800w"
      imagesizes="100vw">

<!-- 2. Preconnect do CDN -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- 3. Fetchpriority dla LCP image -->
<img src="hero.webp" fetchpriority="high" alt="Hero">

<!-- 4. Inline Critical CSS -->
<style>
  .hero-image { width: 100%; height: auto; }
</style>
// 5. Unikaj client-side rendering dla LCP
// ❌ CSR - LCP element renderowany przez JS
function Hero() {
  const [data, setData] = useState(null)
  useEffect(() => { fetchData() }, [])
  if (!data) return null  // LCP element nie istnieje!
  return <img src={data.heroUrl} />
}

// ✅ SSR/SSG - LCP element w HTML
// Next.js / Remix - HTML zawiera już obraz

Checklist LCP:

  • TTFB < 800ms
  • Preload LCP image/font
  • fetchpriority="high" na LCP element
  • Nie lazy-load LCP image
  • Inline Critical CSS
  • Usuń render-blocking JS

Czym jest INP i czym różni się od FID?

Odpowiedź w 30 sekund: INP (Interaction to Next Paint) mierzy responsywność WSZYSTKICH interakcji użytkownika przez całą sesję. FID mierzył tylko PIERWSZĄ interakcję. INP zastąpił FID w marcu 2024. Dobra wartość INP < 200ms.

Odpowiedź w 2 minuty:

Główna różnica między FID a INP polega na zakresie pomiaru - FID mierzył tylko pierwszą interakcję, podczas gdy INP analizuje wszystkie interakcje podczas całej sesji użytkownika. Porównanie obu metryk:

FID vs INP:

FID (First Input Delay) - przestarzałe
├── Mierzy tylko PIERWSZĄ interakcję
├── Często dobry wynik (strona jeszcze nie zablokowana)
└── Nie odzwierciedla prawdziwego UX

INP (Interaction to Next Paint) - aktualne
├── Mierzy WSZYSTKIE interakcje
├── Bierze p75 najwolniejszych
└── Lepiej odzwierciedla rzeczywistość

Co powoduje słaby INP:

// ❌ Long Task blokujący main thread
button.addEventListener('click', () => {
  // Synchroniczna operacja > 50ms
  for (let i = 0; i < 1000000; i++) {
    heavyComputation()
  }
  updateUI()
})

// ✅ Yield to main thread
button.addEventListener('click', async () => {
  // Podziel na mniejsze kawałki
  for (let i = 0; i < 1000; i++) {
    await processChunk(i)

    // Yield co 50ms
    if (i % 100 === 0) {
      await scheduler.yield()  // lub setTimeout(0)
    }
  }
  updateUI()
})

Optymalizacje INP:

// 1. Web Workers dla heavy computation
const worker = new Worker('heavy-task.js')
worker.postMessage(data)
worker.onmessage = (e) => updateUI(e.data)

// 2. requestIdleCallback dla non-critical
requestIdleCallback(() => {
  // Analytics, prefetching, etc.
})

// 3. Debounce/throttle dla częstych zdarzeń
const handleScroll = throttle(() => {
  // Scroll handler
}, 100)

// 4. CSS contain dla izolacji layout
.component {
  contain: layout style paint;
}

Jak unikać Cumulative Layout Shift (CLS)?

Odpowiedź w 30 sekund: CLS mierzy nieoczekiwane przesunięcia elementów. Unikaj przez: wymiary dla obrazów (width/height), rezerwację miejsca na reklamy/embedy, preload fontów, unikanie wstawiania treści powyżej istniejącej.

Odpowiedź w 2 minuty:

CLS obliczany jest jako suma przesunięć wszystkich elementów, które niespodziewanie zmieniły swoją pozycję. Poniższy przykład ilustruje typowy scenariusz, w którym wstawienie reklamy powoduje przesunięcie treści:

CLS = Σ (Impact Fraction × Distance Fraction)

Przykład złego CLS:
┌─────────────────────┐      ┌─────────────────────┐
│      Header         │      │      Header         │
├─────────────────────┤      ├─────────────────────┤
│                     │      │   [REKLAMA]         │  ← Wstawiona
│      Content        │ ──►  ├─────────────────────┤     nagle!
│                     │      │                     │
│                     │      │      Content        │  ← Przesunięty
└─────────────────────┘      └─────────────────────┘

Główne przyczyny i rozwiązania:

<!-- 1. OBRAZY - brak wymiarów -->
<!-- ❌ Powoduje CLS -->
<img src="photo.jpg" alt="Photo">

<!-- ✅ Ustaw wymiary -->
<img src="photo.jpg" alt="Photo" width="800" height="600">

<!-- ✅ Lub aspect-ratio w CSS -->
<style>
  .responsive-img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }
</style>


<!-- 2. FONTY - FOUT/FOIT -->
<!-- ❌ Powoduje CLS gdy font się załaduje -->
@font-face {
  font-family: 'Custom';
  src: url('font.woff2');
}

<!-- ✅ font-display: optional (brak CLS) -->
@font-face {
  font-family: 'Custom';
  src: url('font.woff2');
  font-display: optional;
}


<!-- 3. REKLAMY / EMBEDY -->
<!-- ❌ Brak zarezerwowanego miejsca -->
<div id="ad-container"></div>

<!-- ✅ Zarezerwuj minimum miejsca -->
<div id="ad-container" style="min-height: 250px;"></div>


<!-- 4. DYNAMICZNA TREŚĆ -->
<!-- ❌ Wstawianie powyżej istniejącej treści -->
container.prepend(newElement)

<!-- ✅ Wstawiaj poniżej lub z animacją -->
container.append(newElement)
<!-- lub użyj transform do animacji -->

Animacje bez CLS:

/* ❌ Powoduje reflow/repaint */
.animate {
  animation: bad 0.3s;
}
@keyframes bad {
  from { height: 0; margin-top: 100px; }
  to { height: 100px; margin-top: 0; }
}

/* ✅ Tylko transform i opacity */
.animate {
  animation: good 0.3s;
}
@keyframes good {
  from { transform: translateY(100px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

Optymalizacja ładowania

Czym jest lazy loading i jak go zaimplementować?

Odpowiedź w 30 sekund: Lazy loading opóźnia ładowanie zasobów do momentu gdy są potrzebne (np. obrazy poniżej fold). Native: loading="lazy" dla img/iframe. JavaScript: Intersection Observer. Nie lazy-load zasobów LCP!

Odpowiedź w 2 minuty:

Najprościej zaimplementować lazy loading używając natywnego atrybutu loading="lazy" dostępnego we wszystkich nowoczesnych przeglądarkach. Przykłady użycia:

<!-- NATIVE LAZY LOADING (rekomendowane) -->
<img src="photo.jpg" loading="lazy" alt="Photo">
<iframe src="embed.html" loading="lazy"></iframe>

<!-- Nie używaj dla LCP! -->
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">

Intersection Observer (więcej kontroli):

// Lazy loading z Intersection Observer
const lazyImages = document.querySelectorAll('img[data-src]')

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      img.removeAttribute('data-src')
      observer.unobserve(img)
    }
  })
}, {
  rootMargin: '100px'  // Załaduj 100px przed viewport
})

lazyImages.forEach(img => imageObserver.observe(img))

React - lazy loading komponentów:

import { lazy, Suspense } from 'react'

// Code splitting na poziomie route
const Dashboard = lazy(() => import('./Dashboard'))
const Settings = lazy(() => import('./Settings'))

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

Czym są preload, prefetch i preconnect?

Odpowiedź w 30 sekund: preload - załaduj zasób natychmiast (critical resources). prefetch - załaduj w tle dla przyszłej nawigacji (low priority). preconnect - nawiąż połączenie z serwerem (DNS, TCP, TLS). Używaj oszczędnie - zbyt wiele może zaszkodzić.

Odpowiedź w 2 minuty:

Każda z tych technik służy różnym celom i ma swój poziom priorytetu. Poniższe przykłady pokazują prawidłowe użycie preconnect, preload i prefetch dla różnych scenariuszy:

<!-- PRECONNECT - nawiąż połączenie wcześnie -->
<!-- Użyj dla zewnętrznych domen (CDN, API, fonty) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

<!-- DNS-PREFETCH - tylko DNS lookup (fallback) -->
<link rel="dns-prefetch" href="https://analytics.com">


<!-- PRELOAD - załaduj natychmiast (HIGH priority) -->
<!-- Użyj dla critical resources: LCP image, fonty, critical JS -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero.webp" as="image">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

<!-- Preload z media query -->
<link rel="preload" href="mobile.jpg" as="image"
      media="(max-width: 600px)">


<!-- PREFETCH - załaduj dla przyszłej nawigacji (LOW priority) -->
<!-- Użyj dla next page resources -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/next-page-data.json">

<!-- Modulepreload dla ES modules -->
<link rel="modulepreload" href="/app.js">

Kiedy używać czego:

Technika Priorytet Kiedy używać
preconnect Wysoki External domains (max 2-3)
preload Wysoki LCP image, critical fonts, critical CSS
prefetch Niski Next page resources
dns-prefetch Niski Wiele external domains

Uwagi:

<!-- ❌ Za dużo preload = wolniejsze ładowanie -->
<link rel="preload" href="a.js" as="script">
<link rel="preload" href="b.js" as="script">
<link rel="preload" href="c.js" as="script">
<!-- ... 20 więcej -->

<!-- ✅ Tylko naprawdę critical (2-5 max) -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero.webp" as="image">

Optymalizacja obrazów

Jakie formaty obrazów są najlepsze dla web?

Odpowiedź w 30 sekund: WebP - 25-35% mniejsze niż JPEG, szeroko wspierane. AVIF - 50% mniejsze, rosnące wsparcie. JPEG - fallback dla starszych przeglądarek. PNG - przezroczystość (ale WebP lepszy). SVG - ikony i ilustracje wektorowe.

Odpowiedź w 2 minuty:

Nowoczesne formaty obrazów oferują znacznie lepszą kompresję niż tradycyjne JPEG i PNG. Element <picture> pozwala zdefiniować wiele formatów, z których przeglądarka wybierze najlepszy wspierany:

<!-- PICTURE element dla różnych formatów -->
<picture>
  <!-- AVIF - najlepszy jeśli wspierany -->
  <source srcset="photo.avif" type="image/avif">

  <!-- WebP - szeroko wspierany -->
  <source srcset="photo.webp" type="image/webp">

  <!-- JPEG - fallback -->
  <img src="photo.jpg" alt="Photo" width="800" height="600">
</picture>
Format Kompresja Przezroczystość Animacje Wsparcie
AVIF Najlepsza Tak Tak ~93%
WebP Bardzo dobra Tak Tak ~97%
JPEG Dobra Nie Nie 100%
PNG Bezstratna Tak Nie 100%
SVG Wektorowa Tak Tak 100%

Responsive images:

<!-- srcset + sizes dla różnych rozmiarów ekranu -->
<img
  srcset="photo-400.webp 400w,
          photo-800.webp 800w,
          photo-1200.webp 1200w"
  sizes="(max-width: 600px) 100vw,
         (max-width: 1200px) 50vw,
         800px"
  src="photo-800.webp"
  alt="Photo"
  loading="lazy"
  decoding="async"
>

Narzędzia do optymalizacji:

# squoosh (online/CLI)
npx @squoosh/cli --webp '{"quality": 80}' photo.jpg

# sharp (Node.js)
sharp('input.jpg').webp({ quality: 80 }).toFile('output.webp')

# imagemin (build pipeline)
# Next.js Image component (automatyczna optymalizacja)

JavaScript Performance

Jak JavaScript blokuje rendering?

Odpowiedź w 30 sekund: JavaScript domyślnie blokuje parsing HTML i rendering (parser-blocking). Rozwiązania: defer (wykonaj po parsingu), async (pobierz równolegle, wykonaj natychmiast), dynamiczny import dla code splitting.

Odpowiedź w 2 minuty:

JavaScript domyślnie blokuje parsowanie HTML, co znacząco wydłuża czas do pierwszego renderowania. Atrybuty defer i async pozwalają kontrolować sposób ładowania i wykonywania skryptów:

<!-- ❌ PARSER-BLOCKING (domyślne) -->
<script src="app.js"></script>
<!--
  1. HTML parsing ZATRZYMANE
  2. Pobierz app.js
  3. Wykonaj app.js
  4. Kontynuuj parsing HTML
-->


<!-- ✅ DEFER - pobierz równolegle, wykonaj po parsingu -->
<script src="app.js" defer></script>
<!--
  1. HTML parsing KONTYNUOWANE
  2. Pobierz app.js równolegle
  3. Po zakończeniu parsingu HTML - wykonaj app.js
  4. DOMContentLoaded
-->


<!-- ✅ ASYNC - pobierz równolegle, wykonaj natychmiast -->
<script src="analytics.js" async></script>
<!--
  1. HTML parsing KONTYNUOWANE
  2. Pobierz analytics.js równolegle
  3. Gdy gotowe - PRZERWIJ parsing, wykonaj
  4. Kontynuuj parsing
-->
Wizualizacja:

         HTML Parsing ───────────────────────►

DEFAULT  ████████░░░░░░░░░░░████████████████████
         └─parse─┘└─download─┘└──execute──┘└─parse─┘

DEFER    ████████████████████████████████████░░░░
         └────────parse──────────────────┘└execute┘
                    └─download─┘

ASYNC    ████████████░░░░████████████████████████
         └──parse───┘└exec┘└─────parse──────────┘
              └download┘

Kiedy używać czego:

Atrybut Kiedy używać
(brak) Nigdy (chyba że w <body> na końcu)
defer Główna aplikacja, zależna od DOM
async Analytics, tracking, niezależne skrypty

Czym są Long Tasks i jak wpływają na INP?

Odpowiedź w 30 sekund: Long Task to zadanie JavaScript trwające > 50ms. Blokuje main thread, powodując słaby INP i "zamrożony" UI. Rozwiązania: dziel na mniejsze kawałki, yield to main thread, Web Workers, requestIdleCallback.

Odpowiedź w 2 minuty:

Long Tasks blokują główny wątek przeglądarki, uniemożliwiając przeglądарce reagowanie na interakcje użytkownika. Kluczowym rozwiązaniem jest dzielenie długich zadań na mniejsze fragmenty z wykorzystaniem yield to main thread:

// ❌ LONG TASK - blokuje main thread
function processLargeArray(items) {
  items.forEach(item => {
    heavyComputation(item)  // 1000 items × 1ms = 1000ms!
  })
}


// ✅ YIELD TO MAIN THREAD
async function processLargeArray(items) {
  const CHUNK_SIZE = 100

  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE)

    chunk.forEach(item => heavyComputation(item))

    // Pozwól przeglądarce obsłużyć eventy
    await yieldToMain()
  }
}

function yieldToMain() {
  return new Promise(resolve => {
    // scheduler.yield() (nowsze API)
    if ('scheduler' in window && 'yield' in scheduler) {
      scheduler.yield().then(resolve)
    } else {
      // Fallback
      setTimeout(resolve, 0)
    }
  })
}


// ✅ WEB WORKER dla heavy computation
// main.js
const worker = new Worker('worker.js')
worker.postMessage({ items: largeArray })
worker.onmessage = (e) => {
  updateUI(e.data.result)
}

// worker.js
self.onmessage = (e) => {
  const result = e.data.items.map(item => heavyComputation(item))
  self.postMessage({ result })
}


// ✅ REQUEST IDLE CALLBACK dla non-critical
requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    performTask(tasks.shift())
  }
})

Monitoring Long Tasks:

// PerformanceObserver dla Long Tasks
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log('Long Task:', entry.duration, 'ms')
    // Wyślij do analytics
  })
})

observer.observe({ entryTypes: ['longtask'] })

Narzędzia i monitoring

Jak używać Lighthouse i jakie są kluczowe metryki?

Odpowiedź w 30 sekund: Lighthouse to narzędzie Google do audytu wydajności. Mierzy: Performance (FCP, LCP, TBT, CLS, Speed Index), Accessibility, Best Practices, SEO. Uruchom w DevTools, CLI lub CI. Score 90+ to dobry wynik.

Odpowiedź w 2 minuty:

Lighthouse można uruchomić na kilka sposobów - przez DevTools, CLI lub jako część procesu CI/CD. Poniżej przedstawiono przykłady użycia Lighthouse w wierszu poleceń oraz integracji z GitHub Actions:

# Lighthouse CLI
npm install -g lighthouse
lighthouse https://example.com --output html --output-path ./report.html

# W CI (GitHub Actions)
- name: Lighthouse CI
  uses: treosh/lighthouse-ci-action@v10
  with:
    urls: |
      https://example.com
      https://example.com/about
    budgetPath: ./budget.json

Metryki Lighthouse:

Metryka Co mierzy Cel
FCP First Contentful Paint < 1.8s
LCP Largest Contentful Paint < 2.5s
TBT Total Blocking Time (proxy dla INP) < 200ms
CLS Cumulative Layout Shift < 0.1
Speed Index Jak szybko treść jest widoczna < 3.4s

Performance budget:

// budget.json
[
  {
    "path": "/*",
    "resourceSizes": [
      { "resourceType": "script", "budget": 300 },
      { "resourceType": "image", "budget": 500 },
      { "resourceType": "total", "budget": 1000 }
    ],
    "timings": [
      { "metric": "first-contentful-paint", "budget": 1800 },
      { "metric": "largest-contentful-paint", "budget": 2500 }
    ]
  }
]

Lab data vs Field data:

Lab Data (Lighthouse, DevTools)
├── Kontrolowane środowisko
├── Reproducible
├── Dobre do debugowania
└── Nie odzwierciedla rzeczywistych użytkowników

Field Data (CrUX, RUM)
├── Prawdziwi użytkownicy
├── Różne urządzenia i sieci
├── Odzwierciedla rzeczywistość
└── Trudniejsze do debugowania

Jak monitorować wydajność w produkcji (RUM)?

Odpowiedź w 30 sekund: RUM (Real User Monitoring) zbiera metryki od prawdziwych użytkowników. Narzędzia: web-vitals library, Chrome UX Report, Sentry, Datadog. Monitoruj percentyle (p75, p90), nie średnie.

Odpowiedź w 2 minuty:

Real User Monitoring zbiera dane o wydajności od rzeczywistych użytkowników w produkcji. Biblioteka web-vitals od Google umożliwia łatwe zbieranie Core Web Vitals i wysyłanie ich do systemu analitycznego:

// web-vitals library
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals'

function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,  // 'good' | 'needs-improvement' | 'poor'
    id: metric.id,
    navigationType: metric.navigationType
  })

  // Użyj sendBeacon dla niezawodności
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/analytics', body)
  } else {
    fetch('/analytics', { body, method: 'POST', keepalive: true })
  }
}

onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
onFCP(sendToAnalytics)
onTTFB(sendToAnalytics)

Dashboard metryki:

Monitoruj percentyle, nie średnie!

Percentyle:
├── p50 (mediana) - połowa użytkowników
├── p75 - Core Web Vitals threshold
├── p90 - worst 10%
└── p99 - outliers

Przykład:
LCP p75 = 2.1s ✅ Good
LCP p90 = 3.8s ⚠️ Needs improvement dla 15% użytkowników

Alerting:

// Przykład alertu gdy metryki się pogarszają
if (metric.name === 'LCP' && metric.value > 2500) {
  sendAlert({
    type: 'performance_degradation',
    metric: 'LCP',
    value: metric.value,
    threshold: 2500,
    page: window.location.pathname
  })
}

Zobacz też

Jeśli przygotowujesz się do rozmowy jako Frontend Developer, sprawdź również:


Powodzenia na rozmowie! Web Performance to obszar, który wyróżnia doświadczonych developerów. Pokaż, że rozumiesz Core Web Vitals, potrafisz używać Lighthouse i DevTools, i wiesz jak optymalizować LCP, INP i CLS.

Chcesz więcej pytań rekrutacyjnych?

To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.