Notes · The Algebra of the Interface
published
How Fast Web Apps Boot: The Architecture of Event-Delegated Shells
Dissecting the client architecture of event-delegated app shells — local service-worker restoration, BigPipe streaming, and declarative attribute event routing.
Cache answers to expensive computations, rather than doing them over.
— Butler Lampson, Hints for Computer System Design (1983)
Cite this
Mangalapilly, Y. J. (2026, July). How Fast Web Apps Boot: The Architecture of Event-Delegated Shells. Saṃhitā Notes. https://yesudeep.com/blog/how-fast-web-apps-boot/ @online{mangalapilly2026how,
author = {Yesudeep Jose Mangalapilly},
title = {How Fast Web Apps Boot: The Architecture of Event-Delegated Shells},
journal = {Sa\d{m}hit\=a Notes},
year = {2026},
month = {July},
url = {https://yesudeep.com/blog/how-fast-web-apps-boot/},
urldate = {2026-08-06},
} Yesudeep Jose Mangalapilly. “How Fast Web Apps Boot: The Architecture of Event-Delegated Shells.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/how-fast-web-apps-boot/. TY - ELEC
AU - Mangalapilly, Yesudeep Jose
TI - How Fast Web Apps Boot: The Architecture of Event-Delegated Shells
T2 - Saṃhitā Notes
PY - 2026
UR - https://yesudeep.com/blog/how-fast-web-apps-boot/
Y2 - 2026-08-06
ER - An architectural dissection of event-delegated app shells built on open-source web standards. By the end you'll be able to: explain the app-shell/data split and why local shell navigation costs 0 bytes; describe the open-source jsaction attribute grammar and how one listener at <body> replaces thousands; and state the design law underneath it all — the only runtime contract in the DOM is a closed attribute vocabulary, so everything above it can churn freely.
The fifteen milliseconds
Load an event-delegated web application with the Network panel open and read the first row. performance.getEntriesByType('navigation') reports it precisely:
performance.getEntriesByType('navigation')[0]
// transferSize: 0, responseEnd: ~15 ms, domContentLoaded: ~34 msZero bytes transferred. The document that becomes your application UI never touched the network: a service worker scoped to /app/v1/ intercepted the navigation and answered from a cache. Service Workers accomplish this by intercepting navigation fetches and returning pre-cached HTML documents.
An app shell does not load fast — it restores from local storage, and then asks the network what changed.
The interesting engineering is in what happens next, because a cached shell is only useful if (a) fresh data can arrive fast and paint progressively, and (b) stale HTML can be interactive before the JavaScript that owns it arrives. Event-delegated web shells have a distinct mechanism for each, and both are older and stranger than the component frameworks that rediscovered them.
The shell is a local asset with a version number
Enumerate the caches (await caches.keys()) and the deployment story is right there in the names:
sw-/app/v1/-js-main.en.r1Sm_kuU6nc.es5.OL (78 entries)
sw-/app/v1/-ss-main.-A_y-jQAJic.L.W.O (3 entries)
sw-/app/v1/-js-main.en.4nExO29qKIw.es5.OL (4 entries) ← previous build
sw-/app/v1/ (1 entry: the shell)
Each build's assets live in caches named by a content-hashed build id — and the previous build's caches are still there. Upgrading is a pointer flip to the new cache names; rolling back is flipping to caches that never left. This is atomic deployment and instant rollback implemented at the browser edge, per user. The 78 JavaScript entries are the code-split chunks of the main bundle, each itself content-addressed, so a new build re-downloads only chunks whose hash changed.
Data arrives as a document
The shell paints a frame. Application state arrives over three channels, in order of decreasing urgency: a 76 KB appshell fetch, a 155 KB document loaded into a data iframe at /app/v1/data, and a stream of background API synchronization fetches. The iframe is the fossil worth examining: it is BigPipe-style streaming — the server flushes a document in chunks, and each chunk's inline script hands data to the parent page the moment it arrives, long before the document finishes.
You don't have to take the chunking on faith; event-delegated applications timestamp it in globals, one pair per chunk boundary:
TRACING_FRAME_CHUNK_START / _END
TRACING_RESULTS_CHUNK_START / _END
TRACING_THREAD_DETAILS_CHUNK_START / _END
The frame paints before the thread list, the thread list before the details — and every boundary is measured, because the tracing is part of the protocol, not an afterthought bolted on by a RUM vendor.
Interactive before the JavaScript arrives
Here is the part that should rearrange your priors. Count the framework attributes on the inbox DOM (about 5,300 elements):
for (const a of ['jscontroller','jsaction','jsname','jsmodel','jslog'])
console.log(a, document.querySelectorAll(`[${a}]`).length)
// jscontroller 405, jsaction 460, jsname 135, jsmodel 55, jslog 198There are no components in this DOM. There are annotated elements. The jsaction attribute is a routing table — =eventType:handlerId= pairs — and a single listener at the document root walks up from any event's target, finds the first matching entry, and dispatches. This is jsaction — open-sourced by Google, and since absorbed into Angular as the event-dispatch primitive, where it powers the event replay that ships stable and on-by-default for new SSR projects since v19. The pattern underlying event-delegated web shells is now a mainstream framework's hydration story. Three properties fall out of it:
- One listener, thousands of targets. No per-element listener setup, no listener teardown on DOM changes, O(1) wiring cost for server-rendered HTML of any size.
- Events before code. If you click a row before its controller's chunk has loaded, the dispatcher queues the event, loads the code, and replays it. Server-rendered HTML is interactive by contract, not after a hydration pass completes. (The marker
.CLIENTin the table flags handlers that register at runtime — even the escape hatch is declared.) - Events are messages. Most "event types" in the tables aren't DOM events at all — they're application-level message names. A controller emits a named message; whoever declares that name in its
jsactionhandles it. The DOM is the message bus.
Think of a hotel where no room has its own doorbell wiring. Every door has a card slot (jsaction) naming who should answer, and one concierge (the root listener) watches all the corridors. If the right staffer hasn't arrived for their shift yet, the concierge writes your request down and hands it over the moment they clock in. You never waited on the wiring — the card was the contract.
The discrete math of event delegation: trees, monoids, and quotients
Behind the performance numbers lies a precise discrete mathematical structure:
Ancestor projections on tree metrics. A DOM document is a discrete rooted tree . Traditional event binding attaches stateful closure functions across individual vertices. Event delegation replaces this with a spatial path projection query along tree metrics. When a user gesture triggers at leaf vertex , the root listener walks the unique path to find the minimum-depth ancestor carrying a non-empty
jsactionmapping. Finding a handler is a 1D path-scan rather than a tree-wide traversal.Free monoid queueing and deferred reduction. When an interaction occurs before its handler chunk arrives, the event dispatcher cannot reduce the state immediately. Instead, it appends the tuple to an event queue . This queue forms a Free Monoid over the event alphabet under string concatenation with identity . Replaying events once loads is evaluating a monoid homomorphism that folds the deferred queue over application state :
Quotienting the hydration space. Standard hydration executes component trees to reconstruct event listeners, forcing full execution. Declarative attributes quotient the execution state space by factoring out runtime code execution. The DOM is reduced to an invariable quotient language . The markup becomes a static, non-Turing routing table — converting arbitrary execution into deterministic path lookup.
One namespace, compiled end to end
The same 5–7 character identifiers appear as JavaScript symbols, CSS classes, and attribute values — Closure Compiler and Closure Stylesheets rename the whole program as one unit. Two details matter more than the obfuscation:
jsnameexists so code never queries by styling class. Lookup handles and style hooks are separate vocabularies; CSS can be renamed, purged, or rewritten without breaking a single selector in code. That separation is a design law you can adopt without adopting the compiler.- The thread list under all this compilation is a literal
<table>with<tr role"row">= and eleven cells. The most aggressively compiled page you use daily ships semantic, accessible HTML — the compiler squeezes names, not meaning.
And the strict-CSP posture survives everything above: all 26 script tags on the page carry nonces. No third-party origin executes in the application. (How that intersects analytics — the jslog attribute quietly declaring impression logging on 198 elements, harvested by the same dispatcher — deserves its own piece.)
What event-delegated architectures refuse to do
The dissection method asks: what did the designers refuse, and what did the refusal buy? The jsaction grammar is a static table — no expressions, no conditionals, nothing Turing-complete in the markup. That refusal is precisely what makes the boot path possible: the dispatcher can route (and queue, and replay) without executing a line of application code, so the code can arrive whenever it likes. Compare any framework whose hydration must run components to discover their handlers — the code is on the critical path by construction.
The only runtime contract in an event-delegated DOM is a closed attribute vocabulary. Everything above it — compiler, chunker, service worker, streaming protocol — can churn without breaking the page's meaning.
And the vocabulary is universal: the same attribute grammar runs across both Service Worker app shells and server-side streamed HTML responses. Two distinct boot strategies, one frozen contract in the markup. What the contract buys each of them is a story for another dissection.
Honest limits
This architecture is not free. It assumes one toolchain owning JavaScript, CSS, and markup end to end; adopt the attribute grammar without at least a lint wall and the namespace discipline rots. The compiled identifiers make the page hostile to extension authors and debuggers (view-source tells you shapes, never names). The fourteen iframes isolating chat and companions are an org-chart artifact, not a pattern to copy. And a content site should not want most of this: a blog's fastest boot is plain HTML and no service worker at all. The lesson transfers to apps — surfaces a user returns to daily, where "restore, then reconcile" beats "load."
Lessons
- Boot cost is a design decision, not a performance metric: a SW-cached shell makes navigation a 0-byte local read.
- Version caches by build id and keep the previous build: deployment and rollback become pointer flips at the browser edge.
- Stream data as chunks that paint progressively, and timestamp the chunk boundaries in the protocol itself.
- Delegate events at the root from a declarative attribute table; queue and replay what fires before code arrives.
- Separate lookup names from styling classes; compile names, not semantics.
- The deep law: keep the DOM contract closed and non-Turing — a routing table, not a program — and the entire stack above it is free to change.
Practice
References
- Google & Angular. “Event dispatch source.” Angular. — the living implementation of jsaction's delegation contract, migrated into the Angular monorepo
- Google. “jsaction.” — the archived original implementation
- “Event Dispatch in Angular.” Angular. — the Angular team on SSR event replay and incremental hydration
- Facebook. “BigPipe: Pipelining Web Pages for High Performance.” 2010. — chunked page streaming
- MDN. “Service Worker API.” — the browser's programmable network and cache boundary
- Jake Archibald. “The Offline Cookbook.” web.dev. — cache-then-network patterns used by app shells
- Google. “Closure Compiler.” — whole-program JavaScript renaming
- Google. “Closure Stylesheets.” — whole-program CSS renaming
- Butler Lampson. “Hints for Computer System Design.” 1983. — the source of the article's cache-design epigraph
How to cite
Mangalapilly, Y. J. (2026, July). How Fast Web Apps Boot: The Architecture of Event-Delegated Shells. Saṃhitā Notes. https://yesudeep.com/blog/how-fast-web-apps-boot/ @online{mangalapilly2026how,
author = {Yesudeep Jose Mangalapilly},
title = {How Fast Web Apps Boot: The Architecture of Event-Delegated Shells},
journal = {Sa\d{m}hit\=a Notes},
year = {2026},
month = {July},
url = {https://yesudeep.com/blog/how-fast-web-apps-boot/},
urldate = {2026-08-06},
} Yesudeep Jose Mangalapilly. “How Fast Web Apps Boot: The Architecture of Event-Delegated Shells.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/how-fast-web-apps-boot/. TY - ELEC
AU - Mangalapilly, Yesudeep Jose
TI - How Fast Web Apps Boot: The Architecture of Event-Delegated Shells
T2 - Saṃhitā Notes
PY - 2026
UR - https://yesudeep.com/blog/how-fast-web-apps-boot/
Y2 - 2026-08-06
ER - Webmentions
Annotations
Thank you — your note is held for review and will appear once approved.
Thank you — your note is published.
Please sign in below to leave a note.
