Module VIII · Running

State, storage, and caching

A page is ephemeral: reload it and Module 3 builds a fresh DOM from scratch, with no memory of the last visit. Yet sites remember you — your login, your draft, your offline data. The memory does not live in the page; it lives in the browser, scoped to the origin, surviving reloads, tabs, and restarts. This module is the browser as a filesystem and a database you never provisioned: what a page can store, where, for how long, under whose isolation — the service worker that turns the browser into a programmable proxy, and the problem the whole field agrees is hardest.

8.1 The browser remembers, by origin

Module 7 established the origin as the unit of trust, and storage inherits that boundary completely. Every storage mechanism is partitioned by origin: https://www.democompany.com cannot read what https://api.democompany.com stored, any more than it could read its DOM. The browser is, in effect, a multi-tenant filesystem where each origin gets its own private directory it cannot escape. What differs between the mechanisms is everything else — how much they hold, how you reach them, whether they cost network, and whether a script can read them — and choosing wrong is a performance or security bug waiting to happen.

Client-side storage mechanisms comparedCookies are about four kilobytes, synchronous, and sent to the server on every request. localStorage is about five megabytes, synchronous, not sent to the server. IndexedDB, the Cache API, and the Origin Private File System are large, asynchronous, and not sent to the server.MechanismCapacityAPISent to server?cookies~4 KBsyncYES — every requestlocalStorage~5 MBsyncnoIndexedDBlarge (MB–GB)asyncnoCache APIlargeasyncnoOPFSlargeasyncno
Figure 8.1 — The storage toolbox. Cookies are the only one sent to the server, and they pay for it on every request. The synchronous ones (cookies, Web Storage) block the main thread (Module 6), so the large ones are deliberately asynchronous. sessionStorage is localStorage scoped to a single tab; all of them are partitioned by origin.

8.2 Picking the right drawer

The differences map to clear advice. Cookies are for the small piece of state the server needs on every request — chiefly the session identifier — and Module 7's flags (HttpOnly, SameSite, Secure) are mandatory there. They are a terrible general-purpose store: four kilobytes, strings only, and a network tax on every single request. Web Storage (localStorage / sessionStorage) is the easy key-value drawer for small, non-sensitive preferences — but it is synchronous, so it stalls the thread, and it is readable by any script in the origin, so a successful XSS reads it wholesale. Never put a token or secret in localStorage; that is precisely the data an injected script wants, and unlike an HttpOnly cookie, it is right there.

For real data, IndexedDB is the client-side database: asynchronous, transactional, able to hold structured objects and large volumes, queryable by index. The Cache API stores Request/Response pairs and is the service worker's pantry, the subject of the next section. And the Origin Private File System gives an origin real file-like storage with high-performance access, enough that databases such as SQLite now run in the browser compiled to WebAssembly, reading and writing OPFS as their disk. The browser has quietly grown a complete storage stack — key-value, document database, and filesystem — all behind the origin wall.

Cookies for what the server needs; Web Storage for small preferences; IndexedDB and OPFS for real data. Secrets go in none of the ones a script can read.

8.3 The HTTP cache: the request you skip

Separate from all of that — and often confused with the Cache API — is the HTTP cache, the automatic store the browser manages on its own, the one Modules 2 and 4 kept gesturing at. When the browser fetches a resource, the server's Cache-Control header tells it whether and how long the response may be reused. While a cached copy is fresh, the browser serves it with no network at all — the fastest possible request, because it never happens. When it goes stale, the browser does not simply re-download; it revalidates.

HTTP cache freshness and revalidationWhen a resource is in the cache and still fresh, the browser serves it with no network request. When it is stale, the browser revalidates with a conditional request carrying the ETag; the server answers 304 Not Modified with no body if unchanged, or 200 with a new body if changed.requestin cache& still fresh?yes → serve from cache, zero networkyesstale → revalidate:If-None-Match: "etag"no304: reuse copy200: new body
Figure 8.2 — The HTTP cache. Fresh content costs nothing. Stale content triggers a conditional request carrying the ETag; the server replies 304 Not Modified with no body when nothing changed — a tiny round trip instead of a full download — or 200 with fresh bytes when it did. This is the “Disable cache” checkbox from Module 1, explained.

8.4 The service worker: a proxy you install

The HTTP cache is automatic and the browser owns its rules. The service worker hands those rules to the page. It is a worker in the Module 6 sense — a background script with no DOM access, running off the main thread — but with a special power: once registered, it sits between the page and the network and intercepts every request the page makes. For each one it decides: answer from the Cache API, go to the network, do both, or synthesize a response from nothing. It is a programmable proxy the origin installs onto the user's machine, and it is what makes a web page work offline.

The request resolution pathA request from the page first reaches the service worker if one is registered. The service worker can answer from the Cache API. If it goes to the network, the request passes through the HTTP cache, which may answer from its store, before reaching the network and server. Each layer can answer and stop the chain.Pagefetch()Service Workerintercepts fetchCache API(its pantry)HTTP cachefresh? 304?Network/ serverresponse — from whichever layer answers first
Figure 8.3 — Where a request gets satisfied. A registered service worker gets first say and can answer from the Cache API without touching the network; only if it chooses to fetch does the request reach the HTTP cache and then the network. Each layer can short-circuit the chain — which is exactly how an installed web app loads instantly and works with no connection at all.

The service worker has a lifecycle — register, install (typically pre-caching the app shell into the Cache API), activate, then run, handling fetch events until the browser stops it — and it is restricted to secure contexts: HTTPS only, because a proxy over your requests is far too powerful to hand to an attacker on an open network. It is, in the Module 1 framing, a background daemon the origin installs, sandboxed and event-driven, mediating the page's I/O. Offline-capable, installable web apps — the subject of Module 9 — are built on exactly this.

8.5 Cache invalidation: the hard problem

Caching is free speed until the cached thing changes, and then it is a bug. There is a famous line — only two hard problems in computer science: cache invalidation and naming things — and this module sits squarely on the first. Every cache layer above poses the same question: how do you know the copy you saved is still correct? Serve stale content and users see yesterday's prices; revalidate too eagerly and you have thrown away the speed the cache was for.

The web's practical answers are worth knowing because you will reach for them constantly. Content hashing bakes a hash of the file's contents into its name — main.9f3c1a.css — so a changed file is a new URL the cache has never seen, and you can cache the old one forever because it can never go stale; this is why build tools fingerprint assets. Conditional revalidation with ETag, from Figure 8.2, lets the server cheaply confirm a copy is still good. Service-worker versioning names each cache generation and deletes the old one on activation. Stale-while- revalidate serves the stale copy instantly and refreshes it in the background, trading a moment of staleness for speed. Each is a different point on the same trade-off between fast and fresh.

8.6 The lab: read the browser's memory

DevTools' Application panel is the filesystem browser for this module: Storage shows cookies, Local and Session Storage, and IndexedDB for the current origin; Cache Storage shows what any service worker has stashed; the Service Workers pane shows registration and lets you simulate going offline. In the Network panel, the Size column tells the caching story directly — “(from disk cache)” or “(from memory cache)” for served-without-network, or a small transfer with a 304 status for a revalidation.

8.7 What you now have

The browser as a persistence layer, all of it walled off by origin: cookies for the small state the server needs every request; Web Storage for small, non-secret preferences; IndexedDB and OPFS for real databases and files, asynchronous so they do not stall the thread; the automatic HTTP cache with its fresh/stale/revalidate logic and the cheap 304; and the service worker, a programmable proxy installed onto the machine, intercepting requests and serving from the Cache API to make pages fast and offline-capable. Plus the field's hardest problem, cache invalidation, and the handful of strategies — content hashing, ETags, versioned caches, stale-while-revalidate — that tame it, and the privacy stakes that come with anything a browser can remember.

Storage and the service worker are not just conveniences; they are the foundation of an idea. A site that installs a proxy, caches its own shell, stores its own data, and works offline is no longer quite a document — it is an application, indistinguishable in use from one the operating system installed. Module 9 closes the series on the browser as a full platform: installable apps, the capability and permission model for hardware and sensors, DevTools as the browser observing itself, extensions, and the long argument between the web and native — then hands you back to authoring.

A page forgets everything on reload. The origin forgets nothing unless told to. The browser is the disk the web never had to ask permission to format.