Skip to content

Zero JavaScript Is a Product Decision, Not a Performance Trick

Shipping no client-side script is usually sold as a speed optimisation. The speed is the smallest thing you get. What actually changes is the set of failures your site is capable of having.

Every argument for a zero-JavaScript site starts with a number: a Lighthouse score, a time-to-interactive, a bundle size in kilobytes. Those numbers are real, and they are the least interesting part of the decision.

The interesting part is what the number implies. A site that ships no client-side script has a much smaller set of things that can go wrong, and the shrinkage is not proportional — it is categorical. Whole classes of failure stop being possible rather than becoming less likely.

What stops being possible

Consider the failures a typical content site can have on a given day.

  • A third-party script changes behaviour and your page breaks for a subset of browsers you do not test.
  • A hydration mismatch renders the server’s markup, then replaces it with something subtly different.
  • An analytics bundle blocks the main thread on a mid-range phone for 400ms.
  • Content is rendered client-side, and a crawler that does not execute your JavaScript sees an empty shell.
  • A dependency in your bundle is compromised upstream and now runs in your readers’ browsers.

None of these are exotic. All of them are ordinary. And every one of them requires client-side script to exist. Remove the script and you have not made those failures rarer — you have made them unrepresentable.

That is a different kind of win, and it compounds. A failure that cannot occur needs no monitoring, no runbook, no on-call rotation, and no postmortem. It does not consume attention in the good months, which is exactly when attention is scarcest.

The cost is real and it is specific

This is not free, and pretending otherwise is how people end up resenting the choice six months in.

You give up client-side state. No filter chips that filter without a page load, no live search, no optimistic UI, no anything that depends on knowing what the reader just did. If your product is a dashboard, stop reading — this is not for you.

The honest version of the trade is narrower than the marketing version. It is:

For a site whose job is to be read, cited and crawled, the interactions worth having can be expressed as routes.

A category filter becomes a category page. A tag chip becomes a tag page. A “load more” becomes pagination. Each of those is a URL, which means each of them is a thing that can be linked, shared, bookmarked, indexed and cited. The client-side version of the same feature is a state that exists only inside one reader’s browser and can be handed to nobody.

Framed that way, several of the “features” you give up were never features. They were state that should have been a URL.

Making it a property rather than a preference

The failure mode of this decision is not making it. It is making it and then not keeping it.

Zero JavaScript decays the way every unenforced property decays: through one reasonable exception. Somebody adds an interactive component behind a client: directive because the alternative is three days of work. It ships. Nothing breaks. Six months later there are four of them, a shared runtime, and a build that emits 90KB of script nobody remembers asking for.

The remedy is not discipline. It is a gate. Walk the built output and assert the property directly:

const scripts = walk(dist)
  .filter((f) => f.endsWith('.js'))
if (scripts.length) fail(`dist/ contains ${scripts.length} .js files`)

Four lines, and now the property is checked on every build instead of remembered by whoever reviews the pull request. The exception is still available — someone can delete the assertion — but deleting it is a visible act in a diff, with a reviewer, rather than an invisible one.

This is the general shape and it is worth stating on its own: a constraint you have not encoded is a preference. Write the sentence into a document and it lasts until the first person who has not read the document. Write it into the build and it lasts until someone deliberately removes it.

Where the argument actually lands

The case for zero JavaScript is not that scripts are bad. It is that a site whose job is to be read has almost nothing to spend them on, and every one it does ship buys a permanent liability against a temporary convenience.

If your site is a publication, an archive, a reference, a set of documents — the interactions you need are navigation, and navigation is what the web already does. The script you did not ship is the outage you will not have.

Keep reading

More on Development

Every vertical on this site carries its own writing, its upcoming events and its answers on one page — and the whole archive is available as a feed with a real publication date on every item.