Journal / Technology

Technology

Single-File Architecture: Four Products in Production, Zero Build Step

We run four production applications as single HTML files against Supabase. This is what the architecture actually costs, where it breaks, and the exact conditions under which we would abandon it.

13 min readResolution

Every small team eventually hits the same night: someone has to fix a production bug at 10pm on a Friday. In a conventional modern architecture, between that fix and the user sit a repository, a package manager, a bundler, a transpilation step, a lockfile, a CI pipeline, a deploy platform and a CDN cache. Each stage is defensible in isolation. Together they form a distance — and distance has a maintenance cost even when nobody is touching anything.

We went to the other extreme. Four products in production are single HTML files. Markup, styles and JavaScript in one document, served statically, talking to Supabase through the client library loaded from a CDN. No npm, no bundler, no build. One file goes up over FTP and it is live.

This is not an argument that it is the correct architecture. It is an account of what it costs, and where the limits are that we have already found.

What the architecture actually eliminates

The real saving is not build time. It sits in three less obvious places.

Dependency surface. A freshly scaffolded frontend project arrives with a transitive dependency tree in the hundreds of packages. Every one of those is an update surface, a vulnerability surface and a breakage surface. Our files have exactly two external dependencies: the Supabase client and a webfont, both version-pinned in the CDN URL. That is the entire supply chain, and it fits in a sentence.

Time to the first useful byte of work. Opening the file in an editor and seeing the effect in the browser is a page refresh. There is no dev server, no hot reload that occasionally fails to reload, no corrupted bundler state to clear. The edit-to-observe loop is the shortest one available in web development, and it is short by construction rather than by optimisation.

Resumption cost. This is the item nobody prices in. A product untouched for six months, on a stack with a build step, frequently no longer compiles: the Node version moved, a package was deprecated, the lockfile points at something that has been unpublished. An HTML file from six months ago opens and runs. For an operation maintaining several products with one person giving partial attention to each, that is not convenience — it is whether maintenance is viable at all.

// with build step sourceinstallbundleCIdeployCDN cache // single file index.htmlproduction one transfer
fig. 01 — six stages between a fix and a user, versus one

What it costs

Being precise about this is what separates an account from an advertisement.

There is no static checking. Without TypeScript and without a compilation step, type errors surface in production rather than in the editor. We mitigate with a syntax check on the file before upload and with rigid naming discipline, but mitigation is not a solution. The honest way to size this cost is to count how many of your last twelve months of production bugs a compiler would have caught. If that number is high, the rest of this article does not apply to you.

The file grows. A four-thousand-line HTML file is navigable. A twelve-thousand-line one is not comfortable. Editor search becomes the primary navigation tool, and programmatic bulk edits start to require care — in our case, literal-marker replacement rather than regular expressions, because base64 content embedded in the file breaks naive patterns. That is a direct consequence of the architecture, not an incidental detail.

There is no reuse across products. Four single files mean four copies of the same table component, the same modal, the same currency formatter. One fix has to be applied four times. This is real debt and it grows linearly with the number of products. It is also the single strongest argument against the approach, and we do not have a good answer to it beyond discipline.

Cache becomes your problem. With no content hash in the filename, updates and browser caching collide. A version parameter on the URL solves it, but it is a manual step — and a forgotten manual step is a user looking at last week's build.

Where the line is

The useful question is not whether single-file is good. It is how far it goes. From our experience, the architecture stops paying off the moment any one of these conditions appears:

  1. More than one developer editing the same product concurrently. A single file is a guaranteed merge conflict surface. With one person per product this never happens. With two it happens weekly.
  2. A need for server-side rendering for search visibility. Our products live behind authentication, where indexing is irrelevant. A public content product does not survive this constraint.
  3. Domain logic complex enough to require automated tests. Testing a single file is possible but uncomfortable. Once business rules develop real branching — tax calculation, reconciliation, dynamic pricing — the cost of having no test suite exceeds the cost of a build step.
  4. An authorization surface that row-level security cannot express. All authorization in our products lives in database policies, not in the client. That is what makes the architecture defensible at all: the HTML file is public by definition and holds no secret. If a requirement needs authorization logic the database cannot express, you need a backend — and at that point the architecture has already changed.
crossover 2nd dev · tests · SSR cost of no build cost of build complexity →
fig. 02 — the crossover point: complexity at which a build step starts paying for itself
The load-bearing assumptionSingle-file architecture is not held up by the absence of a bundler. It is held up by the database enforcing every access rule. Remove that and the whole thing becomes an unauthenticated client with opinions.

What this says about architecture decisions in general

The conclusion that matters is not about HTML. It is about the asymmetry between adoption cost and exit cost.

Adopting a framework is cheap and fast; leaving it after two years of code written in its conventions is expensive. Adopting single-file is equally cheap, and leaving is also cheap — the code is ordinary JavaScript, portable anywhere. For an operation still discovering which product will survive, optimising for exit cost is more rational than optimising for operating cost. Large companies do the reverse, correctly, because they already know what they will maintain for a decade.

The question worth asking before choosing any stack is not "does this scale?" It is "if I am wrong about this product in eighteen months, what does changing my mind cost?"

Further reading

Books that shaped this article, including the ones we disagree with. Where a work is popular rather than peer-reviewed, we say so.

John Ousterhout — 2nd ed., 2021
On complexity as the thing you are actually managing. The argument for deep modules over shallow layers is the strongest available case for questioning whether a stage in your pipeline earns its place.
Andrew Hunt & David Thomas — 20th anniversary ed., 2019
Reversibility as a design principle runs through the whole book, and it is the same argument this article makes about exit cost.

Resolution is a participant in the Amazon Services LLC Associates Program. As an Amazon Associate we earn from qualifying purchases — at no additional cost to you. Affiliate links never determine what appears on these lists: several of these books are here specifically because we think they are wrong in an instructive way.

References & notes

  1. Supabase — Row Level Security, official documentation.
  2. Dependency counts, timings and duplication figures referenced here are from our own production repositories at the time of writing.

Corrections are published inline and dated. Write to us if something here is wrong.

// weekly dispatch

One email. Every Tuesday.

The week's analysis, one tool we actually tested, and one behavioural pattern worth practising. Unsubscribe in one click.

// no spam · no resale · ~4 emails a month