Skip to content

Performance

Web performance from the ground up

Measure first with Core Web Vitals, then work through images, fonts, caching, bundles, React rendering and the event loop — each stop removes one reason pages are slow.

8 parts74 min readUpdated Apr 26, 2026

Parts

  1. Part 1

    Core Web Vitals: Understanding LCP, INP and CLS — and How to Fix Them

    The three metrics Google uses to measure real user experience: how they're measured and calculated, why they matter for SEO, and a concrete process to find the real cause before you fix anything.

    11 min

  2. Part 2

    Image Optimization End to End: Formats, srcset, Lazy Loading and next/image

    Which format to pick, how srcset and sizes let the browser choose, when to lazy-load and when not to, reserving space to avoid CLS, image CDNs, art direction with picture, and using next/image in Next.js 16.

    8 min

  3. Part 3

    Web Fonts Done Right: Fast, Stable and Correct for Vietnamese

    How fonts affect LCP and CLS, choosing font-display, preloading the critical font, subsetting with unicode-range for Vietnamese, variable fonts, self-hosting versus CDNs, fallback metric overrides and next/font in Next.js.

    8 min

  4. Part 4

    Caching for Frontend Developers: From the HTTP Cache to CDNs and Data Caches

    Cache-Control, ETags, hashed filenames, s-maxage and stale-while-revalidate at the CDN, service workers, staleTime in TanStack Query — what each cache layer does, which headers to set for each resource type, and the mistakes that leave users looking at stale data.

    8 min

  5. Part 5

    Code Splitting and Lazy Loading in React

    A practical guide to shrinking the JavaScript you ship up front by combining route-level and component-level lazy loading.

    1 min

  6. Part 6

    React Rendering Performance: Measure First, Optimize Second, and Know When to Stop

    What makes a component re-render, why a re-render isn't a DOM update, how to measure with the React DevTools Profiler, the real cost of memo/useMemo/useCallback, splitting context, colocating state, keys in lists, and how the React Compiler changes the picture.

    8 min

  7. Part 7

    List Virtualization from Scratch: Rendering 100,000 Rows Smoothly

    Building a virtual list in React + TypeScript by hand: windowing math with real numbers, overscan, variable heights with prefix sums and binary search, measuring with ResizeObserver, preventing scroll jumps, accessibility — and when not to virtualize at all.

    16 min

  8. Part 8

    The Event Loop, Microtasks and Rendering: Why Your UI Freezes

    A correct mental model of the browser event loop — tasks, microtasks, frames — tested with predict-the-output puzzles, then used to explain why UIs freeze and how to chunk work, yield to the main thread and move heavy work to a Web Worker.

    14 min

Series review

0/16 answered · 0 correct

16 questions drawn from the parts of this series. Answers here count toward each post's own quiz.

  1. Question 1 of 16IntermediateLighthouse on your laptop shows a green LCP, but Search Console flags that URL group red on mobile. What should you trust and do?
  2. Question 2 of 16IntermediateA page has an LCP of 3.5s, of which TTFB takes 1.8s. You compress the hero image heavily but LCP barely changes. Why?
  3. Question 3 of 16IntermediateWhy is sizes needed with a w-descriptor srcset, and what happens without it?
  4. Question 4 of 16IntermediateThe hero image is the page's LCP element. Which setup is best?
  5. Question 5 of 16StarterHow do FOIT and FOUT affect Core Web Vitals?
  6. Question 6 of 16IntermediateWhy does the post treat font-display: swap and fallback metric overrides as a pair?
  7. Question 7 of 16StarterHow does Cache-Control: no-cache differ from no-store, and which do you usually want for HTML?
  8. Question 8 of 16IntermediateHow does the "short-lived HTML, long-lived hashed assets" strategy work, and why keep assets from a few previous deploys?
  9. Question 9 of 16StarterIf you can do only one code-splitting step first, where should you split and why?
  10. Question 10 of 16IntermediateIn the lazy Modal example, why render {showModal && <Suspense>...<Modal/></Suspense>} instead of always rendering <Modal> and hiding it with CSS?
  11. Question 11 of 16StarterA parent component re-renders but the props passed to the child, <Child label="abc" />, are unchanged. Does the child re-render?
  12. Question 12 of 16IntermediateTyping in a search box re-renders the whole dashboard (with a heavy chart) because the state lives at page level. According to the post, which fix comes first?
  13. Question 13 of 16AdvancedIn a variable-height virtual list, a row above the viewport has just been measured and is 80px taller than estimated. If nothing is done, what happens and how is it fixed?
  14. Question 14 of 16IntermediateA user Tabs onto a menu button in one row, then scrolls far away. What happens in a naive virtual list and how is it handled?
  15. Question 15 of 16IntermediateA button handler sets spinner.hidden = false, filters 200,000 rows synchronously for 400ms, then sets spinner.hidden = true. Why does the spinner never appear?
  16. Question 16 of 16IntermediateA loop processes 100,000 items and calls await Promise.resolve() after each one to "avoid blocking the UI". What happens?

Other series

Series