Dualo
Backend Architectures Deep Dive

Server-rendered vs API + SPA vs server components

Three fundamentally different ways to build a web UI — each with its own backend implications for data, routing, and deployment.

1 min read

There are three fundamentally different ways to connect a UI to a backend — not just different frameworks, different ARCHITECTURES. The choice drives how data flows, how many deployments you have, and what your SEO story looks like.

Classic server-rendered (Django templates, Rails ERB, Laravel Blade, PHP, HTMX/Hotwire on top): the server returns ready-to-display HTML per request. Fast first paint, great SEO, simple deployment. Interactivity needs sprinkles of JS or tools like HTMX/Turbo to keep things snappy without a heavy client framework.

API + SPA (Django + React, Rails API + Vue, FastAPI + Next client-only): the backend serves JSON; the frontend is a separate app (React, Vue, Svelte) that fetches, renders, and routes in the browser. Clear separation of concerns, but two codebases, two deployments, two bundle sizes, and all the auth/CORS headaches that come with separation.

Server components / full-stack JS (Next.js App Router, Remix): a newer model where components declare themselves as 'server' (runs on server, streams HTML) or 'client' (hydrated in browser for interactivity). The framework handles the split. Collapses the REST boilerplate FOR FIRST-PARTY UI — but still tightly couples the backend to its own web UI.

The choice isn't religious: classic SSR is having a renaissance (HTMX, Phoenix LiveView) because it delivers SPA-like UX without a massive JS bundle. API + SPA still wins when you have multiple clients (mobile + web). RSC wins for web-first products with heavy type safety needs.

Grounded on https://react.dev/reference/rsc/server-components

Next up

Stateless services & horizontal scaling

To scale horizontally, any request must be servable by any instance. In-memory state breaks this — sessions, caches, and uploads must live outside the process.