Web Performance i Core Web Vitals - Pytania Rekrutacyjne [2026]
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
- Core Web Vitals
- Optymalizacja ładowania
- Optymalizacja obrazów
- JavaScript Performance
- CSS Performance
- Caching i sieć
- Narzędzia i monitoring
- Zobacz też
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ż:
- Kompletny Przewodnik - Rozmowa Frontend Developer - pełny przewodnik przygotowania do rozmowy frontend
- Next.js Pytania Rekrutacyjne - App Router, ISR, optymalizacja
- React Hooks - useEffect, useMemo, useCallback - kiedy NIE używać hooków (performance)
- Najtrudniejsze Pytania JavaScript - event loop, async, memory
- Frontend Testing - Jest, RTL, Cypress - testowanie wydajności
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.
