I work on OnePassApps, so this is our own product, measured on today's build. Everything below was read off the live site: plain HTTPS requests with no JavaScript, plus browser renders in the visible Chrome with JavaScript switched off and then on.
Every route on the site is a React shell with a readable static copy baked into the HTML. This is what the server sends before a single line of our app code runs.
How this was measured
- Plain
GETrequests with a desktop Chrome user agent,Accept-Encoding: identityandgzipcompared, so the wire size and the uncompressed size are both known. - 15 URLs fetched: 14 routes plus a URL that does not exist. The home page and every asset it loads were fetched twice, once uncompressed and once compressed, so the wire size and the uncompressed size are both known; the other routes were fetched again to confirm the served bytes stayed the same.
- Two pages rendered in the visible Chrome with
Emulation.setScriptExecutionDisabledon, and the home page rendered again with JavaScript on, so the "before" and "after" are both what a browser painted, not what the HTML suggests. - Sizes are the bytes the server sent, not what a build on my machine produced.
The shell is a div and a loading status
Every app route answers with the same shape: an empty <div id="root"> holding one static element, a startup shell marked data-toolpass-startup-shell with role="status", aria-live="polite", aria-label="Loading OnePassApps" and aria-busy="true", plus an inline SVG of the Sam mark in a teal gradient. That is 3,622 characters of markup. The visible app replaces it once the bundle mounts.
Before that, a 1,316-byte inline script runs first and forces the dark theme (data-theme="dark" on <html>, data-theme-preset="graphite"), removing any stored light or system preference.
A JSON-LD block ships inline on the same page: a schema.org @graph with 5 nodes of type ItemList, Organization, WebApplication, WebSite.
/guides is the one route that is not a shell at all. It has no #root, loads no app bundle, and its two scripts are the Cloudflare email helper and the analytics beacon. It is a static page.
The readable fallback: the page a crawler and a no-JS visitor get
Each route also carries a data-onepassapps-seo-fallback block with that route's own copy: the title, one line of description, a "What you can do" list, and working links. On the home page it lists NewsFlow and MovieMatcherAI with their "Read the guide" links, all seven studios, "Open OnePassApps", "All apps", and the footer links (Terms of use, Privacy, Refunds and cancellation, Plans and pricing, Support, App guides).
With JavaScript disabled the browser paints it: the home page shows 1,168 characters of text in total, and the fallback element itself computes to display: block. The same happens on /guides, which reads 1,274 characters with links to /guides/newsflow and /guides/movie-tracker and needs no fallback at all.
It disappears the moment scripts run, and it never flashes. shell-fallback.css hides it with :root[data-js] [data-onepassapps-seo-fallback] { display: none; }, and shell-fallback.js sets data-js on <html> as its first statement, before the app bundle is even requested.
The card you get if the bundle never arrives
shell-fallback.js (4,104 bytes raw, 1,613 bytes gzipped) is our own file, served from the origin and loaded before the module entry. It watches for the bundle failing to load, or for our own code throwing before React mounts. If that happens it sets data-shell-failed on <html>, which hides #root and reveals a card cloned from an inert <template id="shell-fallback-template"> that ships in every page.
The card says:
OnePassApps stopped working in this tab
Files and data saved on this device are unaffected. Reloading usually fixes it.
with a Reload button and a Cloudflare-obfuscated email link for the support address. The same switch drives the CSS: .fallback--shell[hidden] stays display: none until data-shell-failed flips it back.
Errors from another origin (Clerk, Cloudflare, a browser extension) are ignored on purpose, because they do not stop the app. In a normal load the card is present in the DOM and hidden: with JavaScript enabled the home page carries it with the hidden attribute set and display: none, and with JavaScript switched off it is never cloned into the page at all.
What the first load actually costs
| Part | On the wire (gzip) | Raw |
|---|---|---|
| Home page HTML | 5,378 B | 18,991 B |
App bundle JS (index-CKM47TUG.js) |
90,779 B | 321,856 B |
App bundle CSS (index-B_evf03w.css) |
42,022 B | 242,648 B |
shell-fallback.js |
1,613 B | 4,104 B |
shell-fallback.css |
1,175 B | 3,054 B |
| Cloudflare email helper | 656 B | 1,239 B |
| Cloudflare Web Analytics beacon | 10,264 B | 31,853 B |
| Total | 151,887 B | 623,745 B |
Two of the four scripts on every page are Cloudflare's, not ours. Our hashed assets are cached for a year (public, max-age=31536000, immutable); shell-fallback.js and shell-fallback.css are cached for four hours with must-revalidate; the beacon for a day.
Two responses of the same page, different bytes
Three GETs of the home page a second apart returned exactly the same number of bytes (18,991) and three different SHA-256 digests. Exactly one line differs between the first two: the hash inside the Cloudflare-obfuscated email link in the crash card.
What I did not check
- One build, one day (04 November 2026), one network. A different edge point or a phone user agent could answer differently.
- The bundle was measured by size only. We did not read or de-minify it, and this post says nothing about what is inside it.
- Only the home page and
/guideswere rendered with JavaScript off. The other routes were measured from their served HTML, not painted. - No Lighthouse run, no real-user timing, and no claim about how any of this behaves on a slow connection.
- The fallback copy is the app's own wording, and the no-JS page is a fallback, not a supported way to use the product.
If you want to see the app itself rather than its shell, the home page is here and the app guides is here.
Top comments (0)