Module 01 intermediate 30 min

The Metrics — Lab vs Field

FCP, LCP, CLS, INP, TTFB, TBT — thresholds, percentiles, PerformanceObserver, and the web-vitals library.

  • TTFB
  • FCP
  • LCP
  • INP
  • CLS
  • TBT

Metrics are only useful when you know what they measure, where (lab vs field), and how to act on them.

Lab vs field vs synthetic

TypeSourceStrengthWeakness
LabLighthouse, WebPageTest, local traceReproducible, debuggableMay not match real devices/networks
Field (RUM)web-vitals, your analytics, CrUXReal users, real conditionsNoisy; needs volume
SyntheticScheduled crawlersTrend monitoringNot your logged-in flows

Core Web Vitals (Google’s UX signals) for ranking/guidance: LCP, INP, CLS — evaluated at 75th percentile (p75) of page loads.

Metric reference

TTFB — Time to First Byte

Server + network latency before HTML bytes arrive. Sub-metrics: DNS, connection, waiting (TTFB proper), download.

Good ≤ 800 ms · Poor > 1800 ms

FCP — First Contentful Paint

First text or image painted. Proxy for “something appeared.”

Good ≤ 1800 ms · Poor > 3000 ms

LCP — Largest Contentful Paint

Largest visible image or text block in viewport. Not always the hero — can be a paragraph.

Good ≤ 2500 ms · Poor > 4000 ms

LCP candidates: <img>, <image> in SVG, poster images, block-level elements with background images, text nodes in block containers.

INP — Interaction to Next Paint

Replaced FID in 2024. p98 of interaction latencies across page lifetime (worst-ish interaction, not average).

Good ≤ 200 ms · Poor > 500 ms

Breakdown per interaction:

INP = input delay + processing + presentation delay
One interaction Input delay Processing Present INP = input delay + processing + presentation delay (worst interaction p98)

CLS — Cumulative Layout Shift

Sum of impact fraction × distance fraction for unexpected shifts (not user-initiated within 500 ms).

Good ≤ 0.1 · Poor > 0.25

TBT — Total Blocking Time (lab)

Sum of (task duration − 50 ms) for tasks between FCP and TTI. Correlates with INP but measured in lab traces.

Good ≤ 200 ms · Poor > 600 ms

Measuring in production

Use the web-vitals library — it handles visibility, bfcache, and metric-specific quirks:

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

function sendToAnalytics({ name, value, id, rating, attribution }) {
  // Your endpoint — include attribution in debug builds
  navigator.sendBeacon('/analytics', JSON.stringify({ name, value, id, rating, attribution }));
}

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

Attribution build (web-vitals/attribution) adds element selectors, URLs, and phase breakdowns — essential for debugging LCP/INP in production.

PerformanceObserver (lower level)

const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime, entry.duration);
  }
});
po.observe({ type: 'longtask', buffered: true });
po.observe({ type: 'event', buffered: true, durationThreshold: 16 });
Entry typeUse for
navigationTTFB, redirect timing
resourceSlow assets
longtaskMain-thread blocking
eventINP debugging (with durationThreshold)
layout-shiftCLS sources

Distributions beat averages

Report histograms or percentiles (p50, p75, p95), segmented by:

  • Device class (mobile vs desktop)
  • Connection (navigator.connection?.effectiveType)
  • Route / template
  • Release version

Averages hide tail latency where INP and support tickets live.

Checklist before optimizing

  1. Is the regression lab-only or confirmed in RUM?
  2. Which phase (load vs interaction vs stability)?
  3. Can you attribute to an element, script URL, or long task?
  4. What’s the business route priority?

Next: Module 02 — loading metrics (TTFB, FCP, LCP). Module 03 — INP and the main thread.

Live on this page

TTFB
FCP
LCP
INP
CLS