How reactolith stacks up against the other ways to drive a React frontend from a server-rendered backend.
reactolith lives in the same neighbourhood as Inertia.js, Hotwire Turbo and htmx. They all share the same instinct: keep the backend in charge of routing, templates, permissions and URLs, and stop maintaining a parallel SPA + JSON API. Where they differ is in what crosses the wire, and what happens to the existing client tree on every visit.
| reactolith | Inertia.js | Hotwire Turbo | htmx | |
|---|---|---|---|---|
| Wire format | HTML | JSON page props | HTML | HTML fragments |
| UI library | React | React, Vue, Svelte | None (vanilla + Stimulus) | None (vanilla) |
| Page navigation | Morph React tree in place | Replace page-level component | Morph DOM in place | Swap a fragment |
| Component state across nav | Preserved | Reset (page component is swapped) | Preserved (DOM-level) | Preserved outside the swapped fragment |
| Need a new backend layer? | No — existing controllers + templates | Yes — an Inertia adapter that returns page props | No | No |
| Real-time updates | Mercure SSE → same render path | Manual / external | Turbo Streams over WebSocket/SSE | SSE / WebSocket extensions |
The Inertia / Turbo / htmx / reactolith family exists for one reason: most teams don’t want to be running two apps. A Rails / Symfony / Laravel / Django app and a React SPA glued to it via a JSON API is a lot of duplicated work — routing twice, validation twice, auth twice, URL helpers twice, deployments twice. DHH made the architectural case for collapsing all of that back into one codebase in “The Majestic Monolith”.
Turbo and htmx make that monolith viable for vanilla DOM. Inertia makes it viable for React/Vue/Svelte at the cost of a JSON page-prop boundary. reactolith makes it viable for React without that boundary — the wire format stays HTML, and the React tree morphs in place across every navigation.