Skip to main content

The SPA pendulum: why we stopped building everything as an application

·1459 words·7 mins
Tech Architecture Web Development Engineering Decisions

For about a decade, the default answer to “how should we build this?” was a Single-Page Application. React, Angular, Vue — pick one, ship a 2MB JavaScript bundle, and feel good about building something that “feels native.” The pride was real. So was the technical debt.

Now the industry is swinging back. Server-side rendering, HTML-first frameworks, and small libraries like HTMX are gaining ground fast, and not because of nostalgia — because a lot of teams shipped SPAs to solve problems that did not require SPAs, and paid for it for years.

I want to document why I think this shift is real, where I landed on the trade-offs, and how I now think about the architecture decision at the start of a project.

How we got here
#

The rough arc is worth recapping because the context shapes the decision.

Before 2010, the web was almost entirely server-rendered: you clicked a link, the server built a full HTML page, the browser displayed it. Every page load was a round trip. It worked, but felt clunky next to desktop software.

Then smartphones arrived, JavaScript engines got fast, and frameworks made client-side rendering approachable. SPAs solved a real problem — they eliminated the jarring full-page reload and made web apps feel closer to native ones. That mattered enormously when the iPhone reset expectations for interaction quality.

The problem is that the industry overcorrected. By 2015 or so, SPAs had become the default even for things that did not need them: blogs, marketing sites, e-commerce pages, internal admin tools. We were shipping 1.5MB JavaScript bundles to display a few paragraphs of text.

The costs compounded:

  • Initial load performance collapsed. A typical SPA in production means loading a near-empty HTML shell, downloading and parsing a large JavaScript bundle, executing it, then making one or more API calls before the user sees anything. On a 3G connection or a mid-range Android device, that sequence can run 5 to 7 seconds — on a slow network with modest hardware, which is not an edge case. SSR delivers a fully rendered HTML page in a single round trip, often under 300ms.
  • SEO became a persistent headache. Googlebot can render JavaScript, but the rendering is deferred and adds crawl budget cost — for content that needs to rank, static HTML is the safer default.
  • Complexity scaled faster than the product did. A simple CRUD application built as an SPA needs a state management library, client-side routing, an API layer, a build pipeline, and often TypeScript on top. The same application built with SSR and a template engine is a fraction of the code and requires far less specialist knowledge to maintain.
  • The device assumption was wrong. SPAs perform fine on a fast laptop with a modern browser; they perform badly on a $150 Android phone with inconsistent connectivity. A large share of global web users — estimates vary, but consistently above half — fall into that second category.

None of this means SPAs are a mistake. It means we applied them too broadly.

What the shift actually looks like
#

The modern SSR approach is not a return to the 1990s. The best current frameworks — Next.js, Nuxt, Remix, SvelteKit — render HTML on the server for the initial load, then hydrate the page with JavaScript so subsequent interactions work without full reloads. You get fast first paint and SEO-friendly markup without giving up smooth in-page behavior where it is actually needed.

The more interesting development is HTMX, which takes a different angle entirely. It is a 14KB library that extends HTML with attributes for making server requests and swapping content into the page — no JavaScript required from the developer. A button that loads more posts looks like this:

<button hx-get="/posts?page=2"
        hx-target="#posts"
        hx-swap="beforeend">
  Load More
</button>

The server returns an HTML fragment; HTMX inserts it. That is the whole interaction. Compared to the equivalent SPA implementation — a fetch call, JSON parsing, a state update, a re-render cycle — it is dramatically simpler. For backend developers who do not specialize in frontend, this matters: they can build interactive interfaces without crossing into a separate framework ecosystem.

HTMX’s GitHub star count went from around 15,000 in 2022 to over 40,000 by late 2024. Star counts are a rough proxy, but that trajectory reflects teams actively looking for something cheaper to maintain after living with the heavy-framework path for a few years.

The decision I now make at the start of a project
#

I use a rough mental filter — not a flowchart, just a set of questions I ask early.

Is the primary purpose content delivery? Blogs, news, documentation, marketing pages, e-commerce product listings — these are content. They need to load fast and rank well. SSR with minimal JavaScript is the right default, and using a SPA here is almost always optimizing for developer preference over user experience.

Is the interface application-like? A collaborative document editor, a real-time dashboard pulling from multiple live sources, a chat interface, a complex multi-step workflow with heavy interdependent state — these are actual applications, and SPAs exist for them because the complexity is justified.

What is the team’s realistic capacity? A small team shipping an MVP does not need to split frontend and backend into separate disciplines with separate deployment pipelines. SSR with HTMX or a lightweight hybrid lets one full-stack developer own the whole thing — not a technical compromise, just a sensible constraint given the context.

Who is the user, and where are they? If a meaningful share of your audience is on mid-range devices or inconsistent networks, you owe them an architecture that does not punish them for their hardware. SSR delivers usable content before JavaScript finishes loading; a SPA does not.

The hybrid approach is often the right answer for anything above a simple content site, and this is where the decision gets cross-wired in useful ways: SSR for public-facing pages, a client-rendered component for interactive sections that genuinely need it, HTMX for lightweight interactions like form submissions or partial page updates. The checkout flow stays server-rendered for security. The product catalog stays server-rendered for SEO. The user dashboard might be client-rendered because it involves real-time data and complex state. These are not inconsistent choices — they are appropriate choices for different parts of the same product.

What I got wrong, and what I would do differently
#

I have shipped SPAs that did not need to be SPAs. The justification at the time was usually something like “we might need this complexity later” or “the team knows React” — neither is a good reason to start with the harder architecture.

The cost shows up slowly. You do not feel it when you ship; you feel it six months later when a simple change requires touching five different layers, when onboarding a new developer takes three weeks instead of three days, or when your Lighthouse scores are embarrassing and you cannot explain to a non-technical stakeholder why the page takes four seconds to load.

I also underestimated how much of the “SPA is necessary” assumption was driven by what was fashionable in the developer community rather than what the product actually required. There is a real social cost to suggesting SSR in a room full of React developers — on one project, the conversation stalled for two weeks while the team debated whether we were “moving backwards,” and we eventually shipped a hybrid that nobody was fully happy with. That friction is not a good reason to make an architectural decision, but it is a real force, and pretending it is not does not help.

If I were starting over on several projects I have shipped, I would default to SSR with HTMX for interactivity, reach for a hybrid framework only when I had a specific reason, and reserve a full SPA for the narrow category of genuinely complex, interactive applications.

The practical takeaway
#

The pendulum swinging back toward SSR is not a repudiation of everything built in the last decade. SPAs solved real problems and will keep being the right answer for genuinely complex, interactive applications.

The shift is more specific: we learned, through collective production experience, that not every webpage needs to be an application. Most of the web is content, and content should load fast, rank well, and work on whatever device the user has. Server-side rendering with minimal JavaScript does that reliably. A 2MB JavaScript bundle does not.

The question worth asking at the start of your next project is not “which JavaScript framework should we use?” It is “does this actually need to run in the browser, or is that just what we are used to building?” Most of the time, the honest answer points toward something simpler.

Related

Why the AI playbook breaks when you scale past a few developers
·811 words·4 mins
Tech AI Scaling
What works for a solo developer or a team of 10 falls apart at enterprise scale. Here is how the bottleneck shifts from personal tooling to messy data pipelines and organizational coordination as you grow.
When bumping PHP memory isn't enough: tracing serialized cache bloat in a page builder
·750 words·4 mins
Tech WordPress Performance PHP Caching
A WordPress 500 traced to PHP memory looked like a simple limit bump — until the real cause turned out to be a page builder serializing large CSS blobs into the object cache on every request.
That 4 MB `options:notoptions` key is why your WordPress site throws a 500 every ten days
·788 words·4 mins
Tech WordPress Redis Performance Caching
An intermittent WordPress 500 that cleared on refresh turned out to be a single 4 MB Redis key growing without a TTL. Here is what the big-keys scan showed, why the mechanism is easy to miss, and the three config changes that stopped it.