AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 28 — Web Performance — Shrinking the Gap Between Ask and See

28 — Web Performance — Shrinking the Gap Between Ask and See

August 12, 202610 min read
Download as Markdown

"Make it fast" was my performance goal, and it was too vague to act on. The split that made it actionable: performance is the gap between "user asks" and "user sees" [1]. Every metric, tool, and optimization is either trying to measure that gap or shrink it. Once I held that split in my head — MEASURE versus OPTIMIZE — the whole field stopped feeling like a grab-bag of tricks.

Here's the way of thinking that organized everything else. Two halves, one loop: I measure to find where the gap is widest, I optimize that spot, I re-measure. The levers on the optimize side are the same three everywhere — cache, compute less, and ship content progressively:

USER ASKS USER SEES the gap to shrink MEASURE find where the gap is widest Lighthouse — scores & audits DevTools — Network & Performance tabs Core Web Vitals: LCP · INP · CLS OPTIMIZE shrink it Cache-Control + Service Worker Streamed responses (chunked) less bytes, less compute re-measure after every change

The rest of these notes just walk the loop: what the gap is made of, how to measure it, and the levers I keep coming back to.

What "fast" actually means — Core Web Vitals

Before any tooling, I had to learn what the gap is made of. It isn't one number. It's three families of feeling, and Google's Core Web Vitals give each a single metric so I can stop arguing about vibes [2][3]:

  • Load speed — how fast did something useful appear? Metric: LCP (Largest Contentful Paint), the moment the biggest visible element paints. Aim under 2.5s.
  • Interactivity — when I click, does it respond now or later? Metric: INP (Interaction to Next Paint), the worst click-to-paint delay across the session. Aim under 200ms.
  • Visual stability — did the content jump while I was reading it? Metric: CLS (Cumulative Layout Shift), how much stuff moved. Aim under 0.1.

That trio is why "the page loaded fast" and "the page felt fast" are different sentences. A page can hit LCP in a second and still feel broken if every tap takes 500ms to register (bad INP), or if the image loads late and shoves the text I'm reading down the screen (bad CLS). The metrics force me to name which kind of slow.

MEASURE — Lighthouse and DevTools

The measure half is two tools, and they do different jobs.

Lighthouse is the automated audit [4]. I run it from DevTools (or the CLI — command line) against any URL and it produces a scored report across Performance, Accessibility, SEO, and PWA (progressive web app — a web app that can work offline) — plus a list of actionable opportunities ("eliminate render-blocking resources," "serve images in next-gen formats"). What I had to internalize: the lab score Lighthouse reports is simulated on a throttled device in a clean room. It's reproducible and great for regression tracking, but it's not what a real user on a real phone experienced. For that I need field data.

DevTools is where the field-grade diagnosis actually happens, two tabs especially [5]:

  • The Network tab shows every request as a waterfall — DNS, connect, request, response — color-coded and sortable. This is where I see the one 3-second image holding up the whole page, or the render-blocking script chain. Disable cache, throttle to "Slow 3G," reload, and the slow resource sticks out.
  • The Performance tab records a flame chart (a timeline showing which functions ran and for how long) of everything the main thread did — the main thread is the browser's single lane for running JavaScript and painting — parse, style, layout, paint, JS execution. When INP is bad, this is where I find the long task (a chunk of work that takes too long) blocking the click handler.

The habit these notes left me with: never optimize blind. Lighthouse tells me what category is weak; the Network and Performance tabs tell me which specific resource or task. Only then do I reach for a lever.

OPTIMIZE — Cache-Control and Service Workers

The biggest, cheapest lever is the same on every project: stop re-fetching what you already have. There are two caching layers, and the line between them took me a while to place.

Cache-Control is an HTTP header on the response — I set it on the server (or CDN), and it tells every intermediary "store this for this long, under these conditions" [6]. The directives I actually use:

Cache-Control: public, max-age=31536000, immutable

That one line covers the case of a hashed asset like /_next/static/chunk-a1b2.js. max-age says cache for a year; immutable says "this will never change, don't even bother revalidating"; public lets the CDN cache it too. For HTML I use no-cache (it can be stored but must revalidate) — because HTML is the one thing that does change, and it's the entry that points to all those immutable assets.

Service Workers are a second cache layer, but they sit somewhere different — inside the browser, as a JavaScript proxy between the page and the network [7]. A service worker can intercept any request the page makes and answer from its own cache before the network is even consulted. That's what makes offline PWAs possible, but the same machinery is a performance win: the second visit can load from the SW cache instantly, with zero network round-trip.

Page makes a request Cache-Control: max-age server / CDN controlled Service Worker intercepts · browser controlled SW Cache (keyed by request) HTTP cache Origin server last resort 1 2 cache hit? 3 miss → network

The clean division I finally landed on: Cache-Control governs the HTTP cache that the browser and CDN already have; a Service Worker is a programmable cache I write myself. For most assets the HTTP cache with good headers is enough. I reach for a Service Worker when I need control the header can't give — offline pages, fine-grained runtime caching, background sync.

OPTIMIZE — streamed responses

The last lever is the one I under-rated longest. Most of my picture of a request was: server builds the whole response, sends it, browser renders it. Streaming breaks that — the server sends data in chunks as soon as each is ready, and the browser can start parsing and painting before the rest arrives [8][9].

The payoff is purely perceived performance, and that's not a slight — perceived performance is the only kind the user feels. A page that streams its shell first shows content in 200ms instead of staring blank for 2s while the full document finishes server-side. The total time is similar; the gap between "user asks" and "user sees something" collapses. In a framework like Next.js this is exactly what loading.tsx, Suspense boundaries (placeholders shown while data loads), and streamed RSC (React Server Components) payloads do — they flush the shell, then stream the slower data in as it resolves.

The same idea powers AI chat token streams and large dataset downloads: ship the first chunk now, keep the connection open, fill in the rest. The browser's Streams API is the low-level primitive that lets client code read these chunks incrementally rather than waiting for the full body [8].

How I use this

The loop is the whole takeaway. When a page feels slow I no longer flail — I run Lighthouse to name the weak category (LCP, INP, or CLS), open the matching DevTools tab to find the specific offender, then reach for the right lever: a Cache-Control header for a re-fetched static asset, a Service Worker for offline or runtime control, or streaming/Suspense for a slow first paint. Then I re-measure. The gap between ask and see is the only thing that matters, and every tool is just a way to see it more clearly.

References

[1] Mozilla, "Web performance," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/Performance

[2] Google, "Core Web Vitals," web.dev, 2024. [Online]. Available: https://web.dev/articles/vitals

[3] Google, "How Core Web Vitals are measured," web.dev, 2024. [Online]. Available: https://web.dev/articles/how-web-vitals-are-measured

[4] Google, "Lighthouse overview," Chrome for Developers, 2024. [Online]. Available: https://developer.chrome.com/docs/lighthouse/overview

[5] Google, "Chrome DevTools," Chrome for Developers, 2024. [Online]. Available: https://developer.chrome.com/docs/devtools

[6] Mozilla, "Cache-Control," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control

[7] Mozilla, "Service Worker API," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API

[8] Mozilla, "Streams API," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API

[9] Mozilla, "An overview of HTTP," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview

Knowledge check · Question 1 of 5

Which Core Web Vital measures visual stability — content jumping around as it loads?

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!