← Back to Blog

The SPA Was a Twenty-Year Detour

The SPA rebuilt routing, templating, and caching in JavaScript, then bolted SEO back on. Where it genuinely won, where it never needed to, and the swing back.

The SPA earned its moment

I want to start by giving the single-page app its due, because the case against it only works if you are honest about the case for it. When Gmail and Google Maps showed up, they made the web feel like real software for the first time. You could drag a map and watch it move. You could read mail without the whole page blinking white and reloading. That was a genuine leap, and the technology behind it was the SPA: load the app once, then talk to the server in the background and repaint only what changed.

For a product that is genuinely an application, this is still the right shape. Rich interactivity, instant transitions between views, the ability to keep working when the network drops, a feel that matches a desktop program. None of that was hype. The SPA delivered something the old request-render-reload web could not, and a generation of impressive products were built on it. I am not here to pretend otherwise.

Then everything became one

The trouble started when the SPA stopped being a tool for building applications and became the default for building anything with a URL. A marketing site became a client-rendered app. A blog became a client-rendered app. A documentation page, a pricing table, a company's about section, all of it shipped as JavaScript that booted in the browser, fetched JSON from an API, and rendered itself on the user's machine.

These are documents. They are text and images that a server could have rendered to HTML and handed over in one round trip, the way the web did from the start. Instead we taught a generation of engineers that the normal way to display an article is to ship a JavaScript runtime that downloads the article separately and assembles it on arrival. The exception had quietly become the rule.

We rebuilt the server in the browser

Once rendering moved to the client, everything the server used to handle had to be rebuilt there. The browser needed a router, so we wrote client-side routers. It needed to turn data into markup, so we shipped templating and a virtual DOM. It needed to fetch and cache data, so we grew whole libraries for request caching and state management that a database and an HTTP cache used to provide for free.

Then search engines could not read pages that did not exist until JavaScript ran, so we invented server-side rendering to draw the first paint on the server after all, plus hydration to wire it back up on the client. Read that sequence again. We moved rendering off the server because the server felt old-fashioned, then reimplemented server rendering inside the client framework to fix what we broke, and called the round trip progress.

The bill: two of everything

A client-rendered site in the maximalist style runs two of nearly everything. There are two routers, one in the framework and one the server still needs. The render path doubles too, a server pass and a client hydration that have to agree exactly or the page flickers and throws warnings. And an entire API layer exists for a single reason: the browser cannot reach the database directly, so every piece of data takes a detour through JSON.

None of that is free. You pay in the bundle the user downloads before seeing anything, in the seconds of hydration before the page answers a click, and in the engineering time spent keeping two render paths in sync. For an application that needs the interactivity, the bill is worth paying. For an article, it is pure overhead charged to the reader.

The swing back is correction, not fashion

What is happening now is the pendulum finding center again. htmx lets the server send HTML over the wire in response to user actions, with no JSON and no client framework required. Hotwire and Turbo do the same for the Rails world. React's own server components push rendering back to the server by default and ship client JavaScript only for the parts that are genuinely interactive. Astro renders static HTML and hydrates small islands exactly where you need them.

The common thread is not nostalgia. Every one of these tools renders on the server because that is where rendering is cheap and fast, and sends JavaScript to the browser only where interaction demands it. That is the shape the web probably should have kept all along, with the SPA reserved for the cases that actually call for it.

Where the SPA genuinely still wins

I have to hold up the other side of this honestly, because plenty of products are real applications and the SPA is right for them. Figma is not a document. A collaborative editor where ten cursors move at once is not a document. A trading dashboard streaming live prices, a design tool, an offline-first field app that has to keep working in a tunnel, a deeply stateful interface where the client holds most of the truth in memory, these earn the architecture outright.

The test is whether the interactivity is the point or the garnish. When the client genuinely owns rich, live, stateful behavior that a server round trip would ruin, ship the SPA and do not look back. The mistake was never building SPAs. It was building them for things that were never applications in the first place.

The question everyone skipped

Underneath all of this is one question that mostly went unasked: is this thing a document or an application? It is the same question the right tool always depends on, and the SPA era answered it the same way every time, regardless of what was actually being built.

Most of what we make is documents with a few interactive parts. A blog with a comment box. A product page with an add-to-cart button. A docs site with a search field. Those are HTML with islands of behavior, not applications that happen to contain some text. Naming which one you are building, before you pick the stack, is the whole decision.

What I reach for now

From the backend seat, my default has swung back with the pendulum. I render on the server, send HTML, and add JavaScript only on the specific elements that have to react without a round trip. The page is fast before any framework boots, search engines read it without special handling, and there is one render path instead of two to keep honest.

When the thing I am building is actually an application, the kind where the client owns live state and interaction is the entire product, I reach for the SPA and pay its costs gladly. The twenty-year detour was not the SPA existing. It was forgetting that most of the web is still, underneath all the JavaScript, a stack of documents.

Share
X LinkedIn HN
UI

Umur Inan

Principal Software Engineer

Backend engineer focused on JVM systems, distributed architecture, and the failure modes that only show up in production. I write about what I learn building and breaking things at scale.

👁 0 5 min read

Comments (0)