Loading — TTFB, FCP & LCP
Render-blocking resources, resource hints, fetchpriority, images, fonts, caching, and delivery architecture for fast first paint.
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/heightoraspect-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
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
| Asset | Cache-Control pattern |
|---|---|
| Hashed JS/CSS | max-age=31536000, immutable |
| HTML | Short max-age + validation or SSG |
| Images | Long cache + CDN; unique URLs on change |
Architecture: SSG, SSR, streaming
| Pattern | Load profile |
|---|---|
| SSG | Best TTFB/LCP for static content |
| SSR | Dynamic HTML; watch server time |
| Streaming SSR | Send 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-displaystrategy - TTFB traced to server vs CDN
Next: Module 03 — INP and main-thread responsiveness.