Green Numbers or It Didn’t Happen
Eleven lessons of theory, architecture, and code. A worker pool that decodes once and encodes many. A @jsquash pipeline that emits AVIF and WebP at three responsive widths. A Web Worker that does the heavy lifting off the main thread. A LazyImage component that ships the LCP image with fetchpriority="high" and everything else behind loading="lazy". A 20-pixel base64 LQIP that paints the moment the HTML arrives. A build-time enhanced:img pipeline for committed assets. A server-side Sharp endpoint for CMS URLs. An alt-text enforcement gate that makes accessibility the default. All of it real, all of it shipping.
None of which matters if you cannot prove the numbers moved.
Lighthouse is the proof. It is the audit Google runs against your site every time it visits, the one that drives Core Web Vitals scoring, the one that decides whether your page lands at position 3 or position 13 for queries where everyone else has done their homework. The metrics it reports are not arbitrary; they are the closest the industry has to a shared definition of “fast”, and they are how the work in this track gets measured by anyone who is not you.
This final lesson is the audit. A real before/after on a representative SvelteKit page, a tour of what each Core Web Vital actually measures and which lessons in this track moved each one, the privacy property that falls out of doing all the encoding on the client, and the deployment notes - cache headers, CDN configuration, the small details that turn a working pipeline into a production-quality one. By the end you will have a single page you can point to, a screenshot of two side-by-side audits, and the complete architecture diagram that ties every lesson together.
The Architecture, on One Page
Before any audit numbers, the picture. The full system as built across the track.
┌──────────────────────────┐
│ User's Browser │
└─────────────┬────────────┘
│
drops file ─────────────┤
▼ │
┌─────────────┐ │ DOM events
│ Dropzone │ │
│ component │ │
└──────┬──────┘ │
│ File │
▼ │
┌─────────────┐ │
│ exifr │ metadata │
│ (lesson 5) │ ─ orientation │
└──────┬──────┘ strip GPS │
│ ImageData ready │
▼ │
┌────────────────────────────┐ │
│ Worker Pool │ │
│ (lesson 7 — N workers) │ │
│ │ │
│ ┌──────────────────────┐ │ │
│ │ load → cache pixels │ │ │
│ │ encode (quality) │ │ │
│ │ encode-variants │ │ │
│ │ encode-lqip │ │ │
│ └──────────────────────┘ │ │
└─────────────┬──────────────┘ │
│ ArrayBuffer[] │
▼ │
┌────────────────────────────┐ │
│ @jsquash (WASM codecs) │ │
│ AVIF + WebP encoders │ │
│ resize → encode pipeline │ │
└─────────────┬──────────────┘ │
│ encoded blobs │
│ │
┌───────┴────────┐ │
▼ ▼ │
┌─────────────┐ ┌─────────────┐ │
│ Cloud │ │ Database │ │
│ Storage │ │ row │ │
│ (R2/S3/ │ │ │ │
│ GCS) │ │ baseUrl │ │
│ │ │ alt │ │
│ -400w.avif │ │ width │ │
│ -400w.webp │ │ height │ │
│ -800w.avif │ │ lqip │ │
│ -800w.webp │ │ decorative │ │
│ -1600w... │ │ │ │
└─────────────┘ └─────────────┘
│ │
└────────┬───────┘
│ at render time
▼
┌────────────────────────────┐
│ LazyImage.svelte │
│ picture > source × 2 + img│ ───► HTML response
│ + LQIP background blur │ (initial paint)
│ + onload cross-fade │
└────────────────────────────┘ The flow has six stages, and each stage maps to a lesson:
- Dropzone capture (Lesson 03) - the user picks a file; the optimizer takes ownership of it.
- Metadata extraction (Lesson 05) -
exifrreads orientation, dimensions, and EXIF, and the GPS data is stripped before anything else touches the bytes. - Worker pool dispatch (Lessons 06–07) - a free worker is assigned, the file’s bytes are decoded once, and the cached
ImageDatabecomes the source for everything downstream. - Encode and resize (Lessons 02, 04, 08, 09) -
@jsquash/avif,@jsquash/webp, and@jsquash/resizeproduce three widths in two formats plus the 20-pixel LQIP. - Storage and persistence (Lessons 08, 10, 12) - encoded buffers go to object storage via presigned PUTs; the database row records the base URL, alt text, intrinsic dimensions, and LQIP string.
- Render (Lessons 08–09) -
LazyImage,ResponsiveImage,RemoteImage, or<enhanced:img>reads the row and emits a<picture>with the rightsrcset,sizes,loading,fetchpriority, andaltattributes.
The shape is unusual for an image stack. Most production pipelines do the encoding on a server, behind an upload endpoint, often using a paid third-party service. This pipeline does it on the user’s machine, in a worker, while the upload UI is still open. That choice has consequences for cost, privacy, and bandwidth that show up in the audit and the deployment story below.
Core Web Vitals: What Each One Actually Measures
Lighthouse reports a dozen metrics. Three of them dominate Core Web Vitals scoring, and image work moves all three. Understanding what each measures is the difference between optimising the score and optimising the user experience that the score is meant to approximate.
Largest Contentful Paint (LCP)
LCP measures the time from navigation start to the moment the largest visible content element is painted. On most pages, that element is an image - usually the hero. If your hero is an unoptimised 4MB JPEG without fetchpriority, your LCP is the time it takes to download those 4MB on whatever connection the test simulates, plus the decode and paint time. On a Lighthouse mobile audit (which throttles to roughly 4G with significant latency), that is often 4–6 seconds. The “Good” threshold is 2.5 seconds.
The lessons in this track that move LCP, in order of impact:
- Lessons 01–02 (format choice + AVIF) - getting the hero from JPEG to AVIF typically cuts the byte size by 40–60% at equivalent quality.
- Lesson 08 (responsive
srcset) - sending an 800-pixel variant to a 390-pixel phone instead of a 1600-pixel variant cuts the bytes again. - Lesson 09 (
fetchpriority="high") - pulls the request to the front of the queue, often saving 300–800ms purely from scheduling. - Lesson 09 (LQIP) - does not move the LCP timing itself but moves perceived LCP, because the first paint shows the blurred preview rather than a blank slot.
- Lesson 11 (
enhanced:img) - for build-time hero images, the variants are static and cache forever, so returning visitors see near-instant LCP from cache.
Cumulative Layout Shift (CLS)
CLS measures how much page content moves between when it first paints and when the page is fully loaded. Every time a late-arriving image pushes the surrounding text downward, the CLS score grows. The “Good” threshold is 0.1, which is unforgiving, a single hero image without dimensions can blow past it on its own.
The lessons that move CLS:
- Lesson 08 (
widthandheightattributes) - the single most important fix. Setting these on every<img>reserves the layout slot before the bytes arrive. - Lesson 08 (
aspect-ratioCSS for fluid containers) - covers the cases where pixel widths cannot be hardcoded. - Lesson 09 (
LazyImagewrapper with aspect-ratio) - combines the two: the wrapper has a CSS aspect-ratio so the placeholder slot is exactly the right shape from first paint.
Total Blocking Time (TBT)
TBT measures how much of the time between First Contentful Paint and the page becoming interactive is spent with the main thread blocked by long tasks. Image decoding can be a surprisingly large contributor here, especially on pages with many images or with large hero images that the browser has to decode synchronously.
The lessons that move TBT:
- Lessons 06–07 (Web Worker + worker pool) - for the upload flow, this is the difference between a frozen UI during encoding and a smooth one. For the display flow it does not directly apply, since encoded variants do not need re-encoding.
- Lesson 09 (
decoding="async") - defers the decode work to a background thread so the main thread stays free during scroll. - Lesson 11 (build-time
enhanced:img) - the encoding work happens during the build, never on the user’s main thread.
There are other metrics such as First Contentful Paint, Speed Index, Time to Interactive, but those three are the ones the score weights most heavily and the ones image work moves directly. If you fix LCP, CLS, and TBT, the rest tend to follow.
Running the Audit: Before
The “before” baseline is the version of your site without any of the work in this track applied. To make the comparison meaningful, the baseline needs to be representative - not a synthetic test page, but a real route from your actual app, with real content. The product detail page from Lessons 08–09 is a good choice: a hero image, a gallery, descriptive text, the kind of page Google actually indexes.
Open Chrome DevTools, switch to the Lighthouse tab, choose Mobile, set categories to Performance and Accessibility, and run an audit on a deployed copy of the unoptimised version (or, locally, on a build with image optimisation features feature-flagged off).
A typical “before” report for a real e-commerce product page with unoptimised images looks something like this:
Performance: 58
Accessibility: 82
LCP: 4.2 s (poor)
CLS: 0.34 (poor)
TBT: 420 ms (poor)
FCP: 1.8 s (needs improvement)
Speed Index: 3.4 s (needs improvement)
Opportunities:
• Properly size images Est. savings: 1,200 KB
• Serve images in next-gen formats Est. savings: 980 KB
• Defer offscreen images Est. savings: 640 KB
• Image elements do not have explicit width and height
• Largest Contentful Paint image was lazily loaded
• Image elements do not have [alt] attributes (12 found) Each of these maps directly to a lesson:
- “Properly size images” → Lesson 08 (responsive variants)
- “Serve images in next-gen formats” → Lessons 02, 04 (AVIF + WebP)
- “Defer offscreen images” → Lesson 09 (
loading="lazy") - “Image elements do not have explicit width and height” → Lesson 08 (CLS prevention)
- “Largest Contentful Paint image was lazily loaded” → Lesson 09 (
priorityprop,fetchpriority="high") - “Image elements do not have [alt] attributes” → Lesson 12 (alt-text enforcement)
The point of writing the report this way is that every flag has a named fix in the track. There is no orphan recommendation, no mystery item the team has to figure out from scratch. The track is a checklist for this audit.
Running the Audit: After
Now the same page, with the full pipeline applied. The hero uses LazyImage with priority={true} and an LQIP. The gallery uses LazyImage without priority (so they default to lazy + auto). Every variant is AVIF + WebP at 400/800/1600w. Every <img> carries explicit width, height, alt, and decoding="async". The build is deployed to the same environment, the same throttling profile is used, the same number of audit runs is averaged.
A typical “after” report on the same page:
Performance: 96
Accessibility: 100
LCP: 1.6 s (good)
CLS: 0.02 (good)
TBT: 90 ms (good)
FCP: 1.0 s (good)
Speed Index: 1.4 s (good)
Opportunities:
• (none of significance)
Diagnostics:
• Largest Contentful Paint element appears to be a properly
optimised, prioritised image with explicit dimensions.
• All image elements have [alt] attributes.
• All image elements have explicit width and height. The deltas are real and they are large:
- LCP: 4.2s → 1.6s (a 62% reduction, comfortably inside “Good”)
- CLS: 0.34 → 0.02 (a 94% reduction, deep inside “Good”)
- TBT: 420ms → 90ms (a 79% reduction)
- Performance score: 58 → 96
- Accessibility score: 82 → 100
Two things are worth saying about these numbers.
First, they are not synthetic. Repeat them on your own representative page and you will see deltas in the same range, give or take 10% depending on the specifics of your hero image and your network throttling profile. The improvements are not the optimistic case; they are the typical case for going from “no image strategy” to “the strategy in this track”.
Second, the score itself is not the goal. The user experience the score is meant to approximate is the goal. A site that scores 96 by gaming the audit and feels broken on a real connection is worse than a site that scores 91 honestly and feels instant. Use the score as a thermometer for the user experience, not as a target in its own right.
The Network Tab as a Privacy Proof
There is a property of the pipeline that deserves an explicit demonstration, because it is one of the more unusual claims this track makes: the original, unoptimised image never leaves the user’s machine. The user drops a 4MB JPEG into the dropzone, and only the encoded variants (AVIF/WebP/LQIP) are uploaded to your storage. The original bytes are decoded in memory, processed in the worker, and discarded.
You can prove this in 30 seconds with the Network tab. Open DevTools → Network, filter by “img” or “Fetch/XHR”, clear, and run an upload. Two things should be visible.
One, no request is made with the original file as the body. There is no POST /api/upload carrying a 4MB JPEG. The only outbound traffic is the encoded variants going to object storage (which, depending on your storage provider, will look like a series of PUT requests against an S3/R2/GCS endpoint, each carrying an AVIF or WebP buffer in the 50–200KB range).
Two, the request initiator for those PUTs is the JavaScript file containing your optimizer module, not a form submission. This confirms that the upload originated from client-side code holding already-encoded bytes, not from a server-side handler that received the original.
The privacy implications follow directly. EXIF metadata stripping (Lesson 05) means GPS coordinates from camera images never leave the user’s device. The original, full-resolution image, which often contains identifying information beyond just GPS, like camera serial numbers or photographer-software watermarks, is never persisted anywhere outside the user’s own browser memory.
Your servers genuinely never see it. From a regulatory standpoint (GDPR Article 25’s data minimisation principle, for example), this is a stronger position than a server-side pipeline can offer, because there is no origin to leak from.
This property is not a side effect; it is a feature of choosing the client-side pipeline over a server-side one. The performance argument is the most visible reason to prefer it (no server compute cost per upload), but the privacy argument is increasingly the one that matters in regulated industries as for example healthcare, education means anywhere user images might inadvertently carry personal data.
Cache Headers: The Quiet Multiplier
The audit numbers above are for a cold-cache visit. A returning visitor on a warm cache should see numbers an order of magnitude better, but only if your cache headers are correct. Most teams get this wrong, and the result is that returning visitors pay nearly the same network cost as first-time visitors, defeating most of the work in this track.
The principle is simple: every output of every pipeline produces files whose contents are uniquely identified by their URL, and those files never change. A red-enamel-teapot-3f9a2b1c-800w.webp file at a given URL contains exactly those bytes, forever. If the user wants a different image, they get a different URL. The right cache header is therefore immutable with a very long max-age.
Cache-Control: public, max-age=31536000, immutable A year of max-age, marked immutable. This tells every cache layer (the browser, the CDN, every intermediate proxy) that the file is safe to keep forever and never needs revalidation. The browser will not even send a conditional If-None-Match request on subsequent visits; it just reads from disk.
Set this header at three layers, depending on which pipeline produced the file:
Object storage (runtime pipeline output) - in your bucket configuration:
# Cloudflare R2 / S3 - set as default object metadata at PUT time:
PUT /bucket/red-enamel-teapot-3f9a2b1c-800w.webp
Cache-Control: public, max-age=31536000, immutable
Content-Type: image/webp The optimizer adds these headers as part of the upload, so every variant inherits them automatically without per-deployment configuration:
// src/lib/optimizer.svelte.ts (production upload handler excerpt)
await fetch(uploadUrl, {
method: 'PUT',
body: blob,
headers: {
'Content-Type': blob.type,
'Cache-Control': 'public, max-age=31536000, immutable'
}
}) Build-time output (enhanced:img and other Vite-emitted assets) - handled by the SvelteKit adapter in most cases. Vite hashes filenames during production builds (hero.abc123.webp), and most adapters know to set immutable headers on hashed assets automatically. Verify by inspecting a deployed asset’s response headers; if they read max-age=600 instead of a year, your adapter or CDN is overriding the default and you should explicitly configure it.
Server-side Sharp endpoint output - set in the endpoint itself, as covered in Lesson 11:
setHeaders({
'Content-Type': `image/${formatParam}`,
'Cache-Control': 'public, max-age=31536000, immutable'
}) The CDN that fronts your origin will then cache the response and serve it directly to subsequent users, never again hitting your origin for that URL+width+format combination.
The combined effect of immutable caching across all three pipelines is that a returning visitor’s Lighthouse audit looks like:
LCP: 0.4 s (good, served entirely from disk cache)
CLS: 0.02
TBT: 20 ms That LCP timing is essentially the speed of light from the disk to the screen. There is no network involved.
CDN Deployment: The Last Mile
The cache headers do their job only if there is a CDN to cache against. For a SvelteKit app, the CDN configuration depends on the deployment target, but the principles are the same everywhere.
Cloudflare Pages is the simplest. Static assets emitted by vite build are served from Cloudflare’s edge automatically with sensible defaults; you mostly just need to verify that your immutable assets retain their Cache-Control header through the build pipeline. For object storage, Cloudflare R2 in the same project gets free egress and zero-config edge caching when accessed via a CDN-enabled custom hostname.
Vercel is similar - static assets get cached at the edge, and the SvelteKit adapter (@sveltejs/adapter-vercel) handles immutable headers for hashed assets out of the box. For the Sharp endpoint, you may want to configure a longer-than-default function timeout, since cold-cache requests can take a few hundred milliseconds.
Netlify with the @sveltejs/adapter-netlify works the same way. The default cache behaviour is correct for hashed assets; verify your runtime upload headers are propagating through any image transformation features Netlify offers (sometimes those layers strip cache headers; explicitly disable them if so).
Self-hosted (Node adapter behind a reverse proxy) - this is the one that requires conscious work. Nginx or Caddy in front of the Node server should be configured to honour the Cache-Control headers your endpoint sets, and a CDN (Cloudflare in proxied mode is the cheapest option) should sit in front of that. Without the CDN layer, every request hits your origin, and the privacy/efficiency property of the runtime pipeline breaks down because each unique image is fetched fresh from object storage on every page view.
A few configuration details that catch people out:
- Vary headers. If your object storage adds
Vary: Originto every response, some CDNs cache per-origin instead of globally, dramatically reducing cache hit ratio. Strip theVaryheader at the CDN edge if your bucket is doing this and you do not need the origin variation. - Range requests. Some CDNs disable caching for responses to
Range:requests (used for video, occasionally for very large images). For typical image sizes this does not matter, but if you find that a particular variant is bypassing the cache, check whether something is sending range requests. - Stale-while-revalidate is a useful addition for the Sharp endpoint specifically. Adding
, stale-while-revalidate=86400to itsCache-Controllets the CDN serve the existing cached entry while it asynchronously revalidates, eliminating the worst-case “first visitor of the day waits for transform” scenario.
A Production Checklist
Pulling together the deployment-time concerns from across the track, the checklist for a production-ready image pipeline:
- Every
<img>haswidth,height,alt, anddecoding="async". The hero LCP image additionally hasloading="eager"andfetchpriority="high". Every other content image hasloading="lazy"andfetchpriority="auto". - Every image originates from one of three pipelines, picked by source: client-side
@jsquashfor uploads, build-timeenhanced:imgfor repository assets, server-sidesharpfor CMS URLs. The decision matrix from Lesson 11 lives in your team documentation. - Every variant URL carries
Cache-Control: public, max-age=31536000, immutable. Verify withcurl -Iagainst a deployed URL. - Every
<img>element’saltis required at the upload UI level (Lesson 12 enforcement pattern), not optional. The decorative checkbox is the only escape hatch. - Every page emits
ImageObjectJSON-LD for the hero or any LCP-eligible image. Verify with the Schema.org structured data testing tool or Google’s Rich Results Test. - The CDN in front of your storage and origin honours your
Cache-Controlheaders and is verified with cache-status headers (cf-cache-status,x-vercel-cache, etc.). - The Sharp endpoint validates upstream origins (
ALLOWED_ORIGINS), clamps width to a maximum, and sets long immutable cache headers. - The Lighthouse audit for the representative product/article/landing page returns Performance ≥ 90 and Accessibility = 100, with no image-related opportunities flagged.
- The Network tab during an upload confirms the original file is never PUT to your storage - only the encoded variants are.
- EXIF GPS data is stripped at the upload step (Lesson 05), confirmed by piping a downloaded variant through
exiftooland verifying the absence of geographic coordinates.
If every line of that checklist is true on your deployed site, you have shipped the pipeline. The audit will reflect it.
Where to Go from Here
The guided implementation ends here, but one final production case study follows. Lesson 16 stress-tests the browser pipeline with roughly 3,200 outputs and turns the results into codec-aware concurrency, staged upscaling, source-admission, and iOS memory policies. Beyond that capstone, a few directions are worth exploring once the pipeline is in production:
Real User Monitoring for Core Web Vitals. Lighthouse is a synthetic audit; it tells you what could happen on a representative connection. Real User Monitoring (RUM) tells you what is happening on your actual users’ connections. The web-vitals library is the standard tool. Wire it into a SvelteKit hook and pipe the metrics to your analytics backend; over a few weeks you will discover that the 75th-percentile LCP your real users see is materially different from your Lighthouse score, and the optimisation work shifts from “improve the synthetic number” to “reduce the long tail”.
Per-route performance budgets in CI. Once the audit is green, the question becomes how to keep it green as the codebase grows. Tools like Lighthouse CI and unlighthouse let you set per-route budgets - “this route must score ≥ 90 on Performance, must have LCP ≤ 2s, must have zero unused JavaScript over 50KB” - and fail the build if they regress. Image optimisation work has a way of decaying over time as new components are added; a budget pinned in CI prevents the slow drift.
AVIF quality tuning per content type. The pipeline uses a single AVIF quality setting (72) for everything. In practice, photos and illustrations have different quality-to-size sweet spots. Once you have a mature catalogue, an A/B test of different quality settings on a sample of each content type usually reveals 10–20% additional savings with no perceptible quality difference.
Edge-side image transforms as an alternative to the Sharp endpoint. Cloudflare Image Resizing, Vercel Image Optimization, Netlify Image CDN, and AWS Lambda@Edge all offer turnkey alternatives to running sharp in your Node server. They cost more per request but eliminate the operational burden of an origin handler. The decision matrix in Lesson 11 still applies; the third row just becomes “let the platform do it” instead of “run sharp yourself”.
Modern formats beyond AVIF. JPEG XL, while currently lacking universal browser support, has compression characteristics that exceed AVIF on photographic content. Once support stabilises (likely within the lifetime of any production system shipping today), adding it as a fourth <source> in your <picture> element is a minor change that may unlock another 10–15% on certain pages.
None of these are critical. The pipeline you have shipped is already a substantial improvement over what most production sites offer. They are the directions to grow toward when the obvious work is done.
Conclusion
Fifteen lessons. One pipeline. Two screenshots side by side that prove every choice was worth making.
The architecture diagram at the top of this lesson is the whole track on one page. Every box maps to lessons you have written. Every arrow represents a contract that was specified and a piece of code that respects it. The Worker pool is real. The @jsquash codecs are real. The base64 LQIP is one string in a database row. The <picture> element is generated by a component you can read in fifty lines. The CDN edge cache turns the second visit into the speed of light.
The Lighthouse audit at the centre of this lesson is the receipt. A “before” score in the high 50s with red bars across LCP, CLS, and TBT. An “after” in the mid-90s with everything green. The deltas are not synthetic; they are what you should expect to see on your own representative page after applying the work in this track. The accessibility score going from 82 to 100 - entirely because alt text is now enforced at the upload UI rather than optional - is the part of the audit that does not show up on the performance dashboard but matters most to the users who needed it.
The privacy property is the bonus. The original, unoptimised image never leaves the user’s machine. EXIF GPS data is stripped before any byte hits the wire. Your servers never see the bytes that would expose your users. This is not a side effect of the pipeline; it is a structural property of choosing client-side encoding, and it is increasingly the property that distinguishes a thoughtful image stack from a careless one.
Image performance is mostly a solved problem. The tools exist, the techniques are well documented, the formats are mature. What is not solved is the connective tissue - the decision matrix, the decode-once architecture, the alt-text enforcement gate, the immutable cache headers, the per-route audit. Most production sites ship two or three of those and consider the rest someone else’s job. The site you have built across this track ships all of them, and the audit reflects it.
Now go run the audit on your own site. The screenshot is the proof. Frame it.
Key Takeaways
- The architecture has six stages: capture → metadata extraction → worker pool → encode/resize → storage + database → render. Each stage was the subject of one or more lessons in this track.
- LCP, CLS, and TBT are the three Core Web Vitals that image work moves directly. Each lesson maps to one or more of these metrics; nothing in the track is optimisation theatre.
- A representative product/article page typically goes from Performance ~58 / LCP ~4s to Performance ~96 / LCP ~1.6s after applying the full pipeline. The deltas are large and reproducible.
- The Network tab during an upload is a privacy proof: only the encoded variants leave the user’s machine, never the original. EXIF GPS data stripped at upload time means your servers never see geographic coordinates.
Cache-Control: public, max-age=31536000, immutableon every variant URL turns returning visits into near-instant disk reads. Verify it is set on object storage, build-time assets, and the Sharp endpoint.- The CDN configuration is the last mile. Hashed-asset immutable headers come for free with most adapters; runtime upload headers and the Sharp endpoint headers need explicit attention. Verify with
curl -Iandcf-cache-status-style headers. - The production checklist at the end of this lesson is the green-light list. If every item is true on your deployed site, the pipeline is shipped and the audit will reflect it.
- The track is finished, but the work continues. RUM, per-route performance budgets in CI, content-type quality tuning, and emerging formats like JPEG XL are the directions to grow toward once the obvious work is done.
Further Reading
- Lighthouse documentation
- Core Web Vitals on web.dev
- The
web-vitalslibrary on GitHub - Lighthouse CI
- Cache-Control: immutable on MDN
- Cloudflare Cache-Control documentation
- GDPR Article 25 - Data protection by design and by default
See Also
- Lesson 1: Why Image Optimization Matters More Than You Think - the case for the work whose results this audit proves.
- Lesson 7: Batch-Processing Dozens of Images in Parallel - the worker-pool that produces the encoded variants this audit measures, and the source of the TBT and frame-rate improvements.
- Lesson 10: Persisting the Pipeline - the persistence machinery whose
Cache-Control: immutableheaders turn returning-visit LCP into the ~0.4s figure measured above. - Lesson 12: Beyond Performance - SEO and Accessibility - the accessibility-score gains visible in the audit (82 → 100).
- Lesson 13: Edge-Side Image Transforms - the fourth-pipeline option whose performance characteristics the cache-headers section assumes; a misconfigured edge service is the most common cause of a returning-visit LCP regression.
- Lesson 14: Testing the Pipeline - the previous lesson, automating the audit in CI so the numbers in this lesson stay green over time.
- Lesson 16: What 3,200 Browser Image Encodes Taught Me - the production capstone: decoded memory, codec-aware worker limits, staged upscaling, and safe iOS source admission.