FSD Frontend System Design

Part III β€” The Speed Β· Lesson 5.5

Network Optimization

In one lineFewer round trips, closer bytes, fewer bytes β€” in that order, because that's the order of impact.

Where you've seen itThe hero image that only starts downloading once the JavaScript that references it has parsed.

  • Time18 min
  • DiagramBefore/after waterfall with preload and parallelisation
  • Chapter5 Β· Performance

Diagram

diagram
BEFORE β€” discovery chain, 3.6s to LCP

  document  β–ˆβ–ˆβ–ˆβ–ˆ
  app.js        β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
  api/config              β–ˆβ–ˆβ–ˆβ–ˆ
  hero.jpg                    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  ← discovered last
  β”œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€
  0   0.5  1.0  1.5  2.0  2.5  3.0  3.6s


AFTER β€” preload + preconnect + parallel, 1.9s to LCP

  document  β–ˆβ–ˆβ–ˆβ–ˆ
  hero.jpg   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ       ← preload in <head>, starts immediately
  app.js     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ     ← in parallel
  api/config β–ˆβ–ˆβ–ˆβ–ˆ           ← preconnect saved the handshake
  β”œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€
  0   0.5  1.0  1.5  1.9s

  Nothing got smaller. The order changed.
diagram
RESOURCE HINTS β€” tell the browser what it can't discover yet

  <link rel="preconnect" href="https://api.example.com">
        └─ DNS + TCP + TLS done early          saves ~100–300ms

  <link rel="preload" as="image" href="/hero.avif" fetchpriority="high">
        └─ start now, I need this for LCP

  <link rel="preload" as="font" type="font/woff2" crossorigin href="...">
        └─ fonts are discovered late (inside CSS) β€” always preload

  <link rel="prefetch" href="/next-page.js">
        └─ idle-time fetch for the NEXT navigation

  Budget: 2–3 preloads. Preloading everything = preloading nothing.
diagram
BYTES β€” the cheap wins, ranked

  1. images     AVIF/WebP Β· responsive srcset Β· lazy-load below fold
                (images are typically 50%+ of page weight)
  2. compress   Brotli > gzip for text Β·  ~15% smaller
  3. fonts      woff2 Β· subset Β· font-display: swap Β· max 2 weights
  4. JS         see 5.7 β€” usually the hardest and most valuable

How it works

  1. Cut round trips first. preconnect to third-party origins, preload the LCP resource and fonts, and break dependency chains so requests start in parallel.
  2. Move bytes closer. A CDN turns a 200ms origin fetch into a 30ms edge hit for everything cacheable.
  3. Then cut bytes. Modern image formats, Brotli, subset fonts, and route-level code splitting.
  4. Set fetchpriority to tell the browser which image matters β€” high on the hero, low on carousels below the fold.
  5. Lazy-load below the fold with loading="lazy" on images and iframes, but never on the LCP element.
html
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high">

<img src="/hero.avif" width="1200" height="600" alt="…"
     fetchpriority="high">                      <!-- LCP: never lazy -->
<img src="/below.avif" width="400" height="300" alt="…"
     loading="lazy" decoding="async">

Trade-offs

TechniqueWinWatch out
preconnect1–2 RTT per originLimit to 3–4; each costs a socket
preloadStarts LCP resource immediatelyOver-use starves other requests
CDN100–300ms per requestCache invalidation strategy
Brotli~15% over gzipSlower to compress β€” do it at build time
loading="lazy"Fewer bytes on first loadApplied to the LCP image, it hurts

Interview angle

"The hero image is the LCP element and it's slow. Fix it."

  • First ask when it starts, not how long it takes β€” usually it's discovered late inside CSS or JS.
  • preload with fetchpriority="high", serve AVIF/WebP with srcset, and set explicit dimensions to protect CLS.
  • Then check the origin: a slow TTFB delays everything downstream regardless of image size.

Recap

  • Round trips beat bytes; discovery order beats file size.
  • Preload the LCP image and fonts, preconnect to third-party origins, cap both.
  • Never lazy-load the LCP element β€” the single most common self-inflicted regression.