Module 02 advanced 35 min

Loading — TTFB, FCP & LCP

Render-blocking resources, resource hints, fetchpriority, images, fonts, caching, and delivery architecture for fast first paint.

  • TTFB
  • FCP
  • LCP

Loading performance is about getting bytes fast and using the main thread minimally until the user sees meaningful content.

TTFB: server & network

TTFB = DNS + connection + waiting (server) + download start.

Levers:

  • CDN / edge caching for HTML and APIs
  • SSR cache (stale-while-revalidate, ISR patterns)
  • Database query optimization — TTFB is often backend-bound
  • HTTP/3 + Early Hints (103) to preload critical assets before full HTML
  • Avoid redirect chains (each adds RTT)
# Early Hints example
Link: </fonts/hero.woff2>; rel=preload; as=font; crossorigin
Link: </hero.webp>; rel=preload; as=image

Render-blocking CSS & JS

CSS blocks rendering until CSSOM is ready (unless media doesn’t match). JS without defer/async blocks HTML parsing.

<link rel="preconnect" href="https://fonts.example" crossorigin>
<link rel="stylesheet" href="/print.css" media="print">
<link rel="modulepreload" href="/chunks/hero.js">

DON’T use @import in CSS — sequential chains delay CSSOM.

LCP optimization

DO:

  • Put LCP <img> in raw HTML (preload scanner discovers it early)
  • fetchpriority="high" on the LCP image
  • Explicit width / height or aspect-ratio (stability + no reflow delay)
  • decoding="async" (default) — sync only if you measure a win
  • Modern formats: AVIF → WebP → fallback

DON’T:

  • loading="lazy" on above-the-fold LCP candidates
  • Client-only render of hero (HTML → JS → fetch image = multi-hop)
  • fetchpriority="high" on everything (zero-sum prioritization)
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" type="image/avif">
<img
  src="/hero.avif"
  alt="Product"
  width="1200"
  height="630"
  fetchpriority="high"
  decoding="async"
>
<img src="/carousel-slide-2.webp" fetchpriority="low" alt="" loading="lazy">

LCP Race — preload + fetchpriority vs default

Hero image load time will appear here.

CSS background as LCP

If LCP is a background-image, preload it:

<link rel="preload" as="image" href="/bg-hero.webp" fetchpriority="high">

Images beyond LCP

<picture>
  <source type="image/avif" srcset="/photo-800.avif 800w, /photo-1200.avif 1200w" sizes="(max-width: 768px) 100vw, 50vw">
  <source type="image/webp" srcset="/photo-800.webp 800w, /photo-1200.webp 1200w" sizes="(max-width: 768px) 100vw, 50vw">
  <img src="/photo-800.jpg" alt="…" width="1200" height="800" loading="lazy">
</picture>

Fonts & FOUT/FOIT

Web fonts often delay LCP (text) or cause CLS (swap).

@font-face {
  font-family: 'Display';
  src: url('/fonts/display.woff2') format('woff2');
  font-display: swap; /* or optional for non-critical */
  size-adjust: 105%;
  ascent-override: 95%;
}
<link rel="preload" href="/fonts/display.woff2" as="font" type="font/woff2" crossorigin>

font-display: optional — skip font if not cached (great for body; risky for brand display).

Metric-adjusted fallbacks (size-adjust, ascent-override) reduce CLS on swap — tools: Fallback Font Generator.

Caching strategy

AssetCache-Control pattern
Hashed JS/CSSmax-age=31536000, immutable
HTMLShort max-age + validation or SSG
ImagesLong cache + CDN; unique URLs on change

Architecture: SSG, SSR, streaming

PatternLoad profile
SSGBest TTFB/LCP for static content
SSRDynamic HTML; watch server time
Streaming SSRSend shell early, stream slow sections
Islands (Astro)Zero JS by default; hydrate only interactive parts

This site is Astro — note how most pages ship no client JS except labs and the Vitals HUD.

Module checklist

  • LCP element in HTML, not client-mounted
  • Preload + fetchpriority="high" on LCP only
  • No lazy LCP; dimensions reserved
  • Critical CSS inlined or preloaded
  • Fonts preloaded with font-display strategy
  • TTFB traced to server vs CDN

Next: Module 03 — INP and main-thread responsiveness.

Live on this page

TTFB
FCP
LCP
INP
CLS