DEV Community

OnePassApps
OnePassApps

Posted on Edited on

23 of 56 Pages Ship No JavaScript: The Guide Fleet Is Static, the App Fleet Is Not

I work on OnePassApps, so this is our own site, and every number below was measured on today's build over plain HTTPS from outside the browser - no session, no cookies, no JavaScript.

Half of our own sitemap ships our application code and half does not, and the line falls in an odd place. Of the 56 URLs in our sitemap, 33 load the app bundle and 23 do not - and the 23 are the guides index plus all 22 individual guide pages. Those are the only routes on the site where a reader gets the whole document with nothing of ours running on top of it.

This is a measurement of the layer between the server and the browser: what each route asks the browser to fetch, and what it leaves out.

How the sitemap splits

Our sitemap answers HTTP 200 with 4,046 bytes and 56 URLs. Reading every one of them and counting the script tags in the served HTML gives two clean groups.

URLs script tags of them ours modulepreload links link tags root div
App bundle 33 6 4 3 10 yes
No app bundle 23 3 1 0 7 no

The 23 routes with no bundle are /guides, /guides/newsflow, /guides/movie-tracker and the 19 other guide pages. Every one of the 33 routes with a bundle is an app page, a Studio page, or one of the four other routes - the home page, the vote board, the plans page and the terms page.

What the 23 routes actually ship

Three script tags each, and only one of them is ours:

  • the JSON-LD block, the single script of ours, carrying the structured data
  • Cloudflare's email-obfuscation helper
  • the Cloudflare analytics beacon

There is no app entry, no fallback script and no preload hint. The modulepreload links are the clearest marker of the difference: the 33 routes that boot the app carry 3 of them, pointing at the runtime, the shared shell and the React vendor chunk, and the 23 routes that do not boot the app carry none. The string modulepreload does not appear anywhere in the 23 served pages.

They also drop the empty mount point: the 0 of 23 that carry a root div element are the app routes, and the guide pages carry the static copy instead. The SEO fallback block that the app routes carry is missing from all 23 of them.

Two things they still take

Dropping the JavaScript does not make the guide pages cheap, because they keep both stylesheets - every one of the 56 pages links the same two: shell-fallback.css and index-B_evf03w.css. The second one is 242,648 bytes raw and 40,833 bytes under brotli, so a reader who opens one guide page still downloads the whole application stylesheet.

The theme is not lost either. All 56 pages arrive with the html element already carrying data-theme="dark" and data-theme-preset="graphite", so the guide pages are dark before the first paint without any script to do it. The app routes additionally run a 1,316-byte inline script that forces that theme and drops a stored light preference; the guide pages have no such script, because they do not need one. The startup-shell marker is on all 56 pages either way.

What the app routes pull instead

The 33 routes that boot the app point at five hashed files:

  • /assets/app-shell-shared-DCrZF6q_.js - 369,609 bytes raw, 116,569 bytes under brotli
  • /assets/index-B_evf03w.css - 242,648 bytes raw, 40,833 bytes under brotli
  • /assets/index-LNYK_bk0.js - 324,591 bytes raw, 90,675 bytes under brotli
  • /assets/react-vendor-DY6N_LXH.js - 178,924 bytes raw, 56,171 bytes under brotli
  • /assets/rolldown-runtime-QTnfLwEv.js - 694 bytes raw, 403 bytes under brotli

That is 873,818 bytes of JavaScript raw and 263,818 bytes under brotli, plus 242,648 bytes of stylesheet. All five come back with cf-cache-status: HIT, so the edge serves them rather than the origin.

The two fleets do not weigh the same

Group URLs bytes, uncompressed bytes, brotli
App pages 22 19,132 to 19,576 5,434 to 5,574
Guide pages 22 26,600 to 32,451 6,768 to 7,613
Studio pages 7 17,285 to 17,538 5,022 to 5,126
Other routes 5 16,380 to 75,113 4,689 to 18,221

The five other routes are the home page, the vote board, the plans page, the terms page and the guides index, and the guides index is the one of them without a bundle.

Across all 56 pages that is 1,386,556 bytes uncompressed, 369,515 bytes under gzip and 362,691 bytes under brotli. Brotli buys 6,824 bytes over gzip on our HTML, which is 1.8 percent - nowhere near the 70 percent it saves on the hashed JavaScript. The HTML is mostly one repeated shell, and compressors already do well on repetition.

Read the other way round, the text the pages actually show is bigger in the static fleet. Stripping the tags out of the served HTML leaves 2,781 to 3,395 characters of readable text on a guide page and 1,778 to 2,088 on an app page. The app pages are the interactive ones, and the guide pages are the ones that say more before anything runs.

Three smaller facts

  • All 56 pages answer with transfer-encoding: chunked and none of them sends a content-length, so a client cannot know the size of the body before it finishes.
  • Gzip and brotli are both honoured on every page. Zstandard is not: no page came back with a zstd Content-Encoding when asked.
  • The two fallback files are served with a four-hour window rather than the pages' max-age=0: shell-fallback.css is 3,054 bytes and shell-fallback.js is 4,104 bytes, both public, max-age=14400, must-revalidate. The validators on this host are uneven: none of the 56 pages carries an ETag, all 5 hashed assets carry a strong one, the three text files a crawler reads - robots.txt, llms.txt and sitemap.xml - carry strong ETags too, and the two fallback files carry weak ones.

What I did not do

One build, one day, 8 November 2026, plain HTTPS from one network with no signed-in session. Each URL was fetched four times - once plain, then once each asking for gzip, brotli and zstd - so the byte totals are what the server sent in that window, not a fixed property of the build. Nothing here says what a page looks like once the app code runs, or how any crawler uses what it found; both need a browser and a reader, and this was neither. The zstd result is one request per URL, not a sweep of the encodings the origin supports. The guide pages are static documents and I did not open one in a browser to confirm what a reader sees.

Try it yourself: the app guides, and the site home.

Disclosure: I work on OnePassApps.

Top comments (0)