What I Learned Replacing a React SPA with Hotwire
A client had a React SPA built three years ago by an agency that had moved on. Next.js, 900+ npm dependencies, a GraphQL layer talking to a Rails API, and a deployment pipeline held together with duct tape. The app was a content-heavy dashboard — login, view reports, update settings, download CSVs. No real-time collaboration. No drag-and-drop. Nothing that justified a 2.3 MB JavaScript bundle and a 14-second load time on 3G.
I replaced the entire React frontend with Rails and Hotwire. And I’d do it again.
What Got Simpler
Almost everything. Build tooling went from a 400-line Webpack config to the Rails asset pipeline. State management disappeared — a Redux action/saga/store/re-render chain became a form submission returning a Turbo Stream. The JSON API layer went away entirely because the server just renders HTML now. Deployment went from two apps and a 12-minute pipeline to one app and four minutes.
What Got Harder
The mental model shift is real. Instead of “what state does this component need?” you’re asking “what HTML should the server send back?” Developers who’ve only worked in SPAs find that disorienting at first.
Complex interactions require more thought too. Turbo Frames and Streams cover a lot of ground, but when you need something truly dynamic, Stimulus is powerful but more imperative than React’s component model. You also need discipline about where logic lives — split between server partials and Stimulus controllers, it can get messy if you’re not deliberate.
The Results
Six weeks, one developer. The original took a team of three about four months.
- Page load: 14s to under 2s on 3G
- JavaScript: 2.3 MB to 47 KB
- Dependencies: 900+ npm packages to ~30 gems
- Code: ~60% reduction
More importantly, the client’s junior Rails developers could read and ship changes on day one. That was never true with the React app.
Who This Is For
Not everyone. If you’re building Figma or a collaborative editor, you need a client-side framework. But most apps aren’t Figma. Most apps are CRUD with a nice coat of paint, and shipping a megabyte of JavaScript to manage state the server already knows about is solving a problem you don’t have.
Hotwire is delightfully boring. After years of fighting bundlers and hydration errors, boring is exactly what I want.