Custom Software · Architecture · Performance · SME

We stopped shipping SPAs for SME platforms: what server-rendering actually changed

Published · Updated

Across four SME platforms we rebuilt as server-rendered apps, time-to-interactive dropped by roughly 60%, hosting bills halved, and one whole class of bugs disappeared. Here are the numbers and the trade-offs.

Two years ago our default stack for client platforms was a React SPA talking to a JSON API, because that is what the industry default looked like. Over the past eighteen months we have rebuilt or launched four SME platforms as server-rendered applications instead. This is what actually changed, measured rather than vibes.

The baseline problem

A typical SME platform we inherit is a marketing site plus a logged-in area: dashboards, document upload, maybe a quoting tool. As an SPA this means two deployables, a CORS policy, duplicated validation on client and server, and a hydration step that pushes 300 to 600 KB of JavaScript to a user who mostly wants to read a page. On the last one we audited, first contentful paint on a mid-range phone was 4.1 seconds on 4G.

What we changed

We moved to server-rendered HTML with small islands of client-side interactivity only where they earn their keep: a live search box, a multi-step form, a file uploader with progress. Forms post to the server. Validation lives in one place. Authentication is an HTTP-only cookie set at login, not a token juggling exercise in localStorage. The framework matters less than the discipline: render on the server, enhance on the client.

The measured differences

Across the four rebuilds, median time-to-interactive went from 3.8 seconds to 1.5 seconds on the same hosting tier. JavaScript shipped per page dropped from an average of 410 KB to under 60 KB. Hosting costs fell by roughly half, because we deleted the separate API tier, the SPA static hosting, and one of two CI pipelines. One client went from two Render services and a Netlify site to a single €25-per-month VPS with headroom.

The bugs that disappeared

The category that vanished entirely was state synchronisation: stale client caches showing data the server had already changed, optimistic updates that never reconciled, forms that validated green in the browser and red in the API. In the SPA versions these generated a steady trickle of support tickets, roughly three to five a month per platform. Post-rebuild, that class of ticket is effectively zero. We did not fix the bugs; we deleted the architecture that produced them.

Security got simpler too

Removing the token-in-browser pattern removed our most common audit finding. With session cookies, CSRF protection, and server-side authorisation checks on every render, the attack surface shrank to something a small team can reason about. Our last penetration test on a server-rendered platform found two medium issues; the comparable SPA it replaced had nine, four of them variations on token handling.

Where the SPA still wins

Honesty requires the counterpoint. For genuinely application-like surfaces, such as a drag-and-drop planner or a real-time collaborative view, the SPA model earns its complexity. We kept one client on a React front end for exactly that reason. The rule we now apply: if more than half the screens are read-mostly, server-render it. If the product is the interaction itself, pay for the SPA.

Takeaway

Before defaulting to a SPA, count your read-heavy screens and your deployables. For most SME platforms we see, server-rendering cuts load time by half or more, halves hosting cost, and removes entire bug categories, at the price of giving up client-side routing theatrics nobody asked for. Boring rendering is a feature, not a compromise.

Working on a project where these methods apply?