Why I Stopped Defaulting to React for Every Project

After years of watching teams drown in JavaScript complexity, I'm back to building most apps with server-rendered frameworks—and shipping faster than ever.

An opinionated guide to choosing between SPAs and server-side rendering based on real production experience. Learn what breaks, when JavaScript frameworks are actually worth it, and why Rails-style frameworks are thriving again.

Jay McBride

Jay McBride

Software Engineer

10 min read
Support my work on Buy Me a Coffee

Introduction

I’ve watched three separate teams spend six months building features that could have shipped in six weeks with a traditional server-rendered stack.

The pattern is always the same. Someone says “we should use React” before anyone knows what they’re actually building. Next thing you know, you’re configuring Webpack, debugging hydration errors, writing API endpoints that return JSON instead of just rendering the damn template, and explaining to stakeholders why the site takes three seconds to show anything.

Six months later, you realize most pages are just forms and lists. Nothing particularly interactive. Nothing that needed client-side routing. Just data in, data out.

This article is for developers who’ve shipped production JavaScript applications and are wondering why it felt harder than it should have been. If you’re still learning React, this isn’t your starting point. Go build something with it first, then come back when you’ve debugged your first SSR hydration mismatch.

I’m going to tell you when SPAs are absolutely the right choice, when they’re complete overkill, and what breaks when teams choose wrong. No “it depends” hedging. Just what I’d actually build today.

Enjoying this? 👉 Tip a coffee and keep posts coming

Here’s who this is for: Mid-level to senior developers building real products. People who’ve seen React apps succeed and also seen them become unmaintainable nightmares. CTOs trying to figure out why frontend velocity has dropped to zero.

Not for: Beginners looking for framework comparisons. This assumes you already know what SPAs and SSR are.

The question isn’t “what’s better?” It’s “why did we make this so complicated?”


The Core Judgment: Default to Server Rendering Unless You Have a Specific Reason Not To

Here’s my default recommendation after shipping both approaches multiple times: build server-rendered apps with targeted JavaScript enhancements, and only reach for a full SPA framework when you hit a concrete limitation.

Not theoretical. Concrete.

Most teams do this backwards. They default to React because it feels “modern” or “future-proof,” then spend months dealing with complexity they never needed. I’ve done this. Multiple times. Every time, I regretted it.

SPAs are complex infrastructure. You need build tooling or nothing works. You need state management or data gets out of sync. You need routing or the back button breaks. You need SSR for SEO or Google can’t index you. You need hydration logic or forms don’t work without JavaScript. You need code splitting or your bundle is 2MB. You need a backend API or you have nothing to fetch from.

Server rendering gives you most of this for free. URLs work. Forms work. The back button works. SEO works. Progressive enhancement is trivial.

The mistake people make is thinking client-side routing and component state are features. They’re not. They’re tradeoffs. You get instant page transitions and optimistic UI updates in exchange for significantly more complexity. If your app doesn’t need those things, you paid for nothing.

I see this constantly: teams build React applications where 80% of the pages are forms that POST data and redirect. Blog posts. Dashboards with tables. Settings pages. None of these need client-side JavaScript to function. They benefit from it sometimes, but they don’t need it.

The decision isn’t “which is more powerful?” It’s “which complexity am I willing to maintain for the next three years?”

If you’re building Google Docs or Figma, use React. If you’re building a SaaS app with forms and CRUD operations, start with Rails or Laravel and add JavaScript where it actually improves the experience.


How This Works in the Real World

The reason SPAs feel inevitable is that we’ve forgotten what good server rendering looks like.

You think server rendering means full page refreshes. Slow, janky transitions. No interactivity. But that’s not what modern server frameworks do anymore.

Here’s what actually happens with tools like Turbo or Livewire:

User clicks a link. JavaScript intercepts the click, fetches the next page in the background, swaps out the content, and updates the URL. No white flash. No spinner. No React bundle. Just instant navigation that feels like an SPA but requires about 3KB of JavaScript total.

User submits a form. JavaScript intercepts the submit, sends the POST request, receives HTML back, swaps in the new content. Validation errors render exactly where they belong. No useState, no form libraries, no client-side validation that duplicates your server logic.

When you need actual interactivity—modals, dropdowns, autocomplete—you add Alpine.js or Stimulus. Tiny libraries that give you reactive behavior scoped to individual components. No build step. No virtual DOM. Just progressive enhancement.

What surprised me when I went back to this approach:

  1. Development velocity tripled. No waiting for the build. No prop drilling. No deciding which data belongs in Redux vs local state. Just write the view with the data you have.

  2. Bugs dropped dramatically. When your backend renders the HTML, there’s no state sync problems. No race conditions between client and server. No hydration mismatches. What the server sends is what the user sees.

  3. SEO and performance became non-issues. Every page is server-rendered by default. No SSR configuration. No pre-rendering. No waiting for JavaScript to load before content appears. Just fast, crawlable HTML.


A Real Example: When I Chose Wrong

I built a content management system for a client in 2021. They needed editors to create articles, manage media, configure permissions. Standard CMS stuff.

I chose React because I’d been using it for everything. Felt natural. Seemed like the “professional” choice.

Three months in, we were nowhere near done. Every form needed validation on the client and server. Every data fetch needed loading states and error states. Image uploads required progress bars and retry logic. The bundle was 400KB even after code splitting. Editors complained about the spinner every time they clicked anything.

I rebuilt the core functionality in two weeks using Rails with Hotwire. Same features. No JavaScript framework. The complex parts—image cropping, rich text editing—used focused libraries (Cropper.js, Trix). Everything else was just server-rendered HTML with Turbo for smooth navigation.

What I’d do differently: Ask “does this actually need to be an SPA?” before writing a single line of JavaScript. If the answer is “no,” use server rendering. If the answer is “partially,” use server rendering with targeted JavaScript enhancements. Only use a full SPA framework when the answer is genuinely “yes.”

The CMS didn’t need React. It needed forms, lists, and uploads. Rails already does that.


Common Mistakes I Keep Seeing

Building an API when you don’t need one. If your frontend and backend are deployed together, you probably don’t need a JSON API. Just render the template. You save hundreds of hours not writing serializers, not documenting endpoints, not syncing types between frontend and backend.

Using client-side routing for content sites. Blogs, marketing sites, documentation—these don’t need client-side routing. The performance cost of JavaScript execution is higher than the benefit of instant transitions. Just use server navigation. Turbo gives you smooth transitions without the complexity.

Treating interactivity as all-or-nothing. Teams think “we need some JavaScript, so we should use React everywhere.” No. Use JavaScript where you need it. A modal component doesn’t require React. Alpine.js is 15KB and handles it fine.

Optimizing for the wrong metric. SPAs optimize for “instant page transitions” but most users don’t care if navigation takes 100ms vs 10ms. They care if the site loads fast initially, works without JavaScript, and doesn’t break when they click the back button. Server rendering optimizes for those things by default.


Tradeoffs and When SPAs Are Actually Worth It

I’m not saying SPAs are bad. I’m saying they’re overused.

Use an SPA framework when:

  • You’re building an application with complex client-side state (collaborative editing, real-time dashboards, canvas-based tools)
  • Most user interactions don’t involve server round trips (drawing apps, games, client-only tools)
  • You need offline functionality with service workers and local storage
  • Your team is already expert in React and you’re not starting from scratch

Don’t use an SPA framework when:

  • You’re building a content site, blog, or marketing pages
  • Most pages are forms, tables, and CRUD operations
  • SEO and initial load performance matter
  • Your team is small and you need to ship fast

Real limitations of server rendering:

  • Latency matters more. Every interaction that updates UI hits the server. For US-based servers serving global users, this can feel slow.
  • Real-time collaboration is harder. Syncing state between multiple users requires WebSockets or polling anyway, so you might as well use a client-side framework.
  • Complex animations and transitions require JavaScript regardless. But you can use lightweight libraries without adopting React.

The honest answer: most web applications are 80% standard CRUD and 20% interactive features. Build the 80% with server rendering and progressively enhance the 20% with focused JavaScript.


Best Practices I Actually Follow

Start with plain server rendering. Build the core functionality without JavaScript. This forces good architecture and ensures the app works for everyone.

Add Turbo or Livewire for smooth navigation. Get SPA-like page transitions with minimal code.

Use Alpine.js or Stimulus for scoped interactivity. These libraries give you reactive components without a build step or framework lock-in.

Defer to libraries for complex widgets. Need a rich text editor? Use Quill or Trix. Date picker? Use Flatpickr. Don’t build these from scratch in React.

Measure actual performance, not perceived complexity. Server-rendered apps often feel “simpler” to build but seem “slower” because developers are used to instant client-side updates. Measure real user experience with Lighthouse and RUM data, not gut feeling.


Conclusion

JavaScript frameworks aren’t the problem. Defaulting to them without thinking is.

React, Vue, and Svelte are powerful tools. They solve real problems for real applications. But they also introduce complexity that many projects don’t need and can’t justify.

After years of building SPAs, I’ve learned that server rendering with targeted JavaScript enhancements ships faster, breaks less, and scales just fine. The hype moved on from Rails and Laravel, but those frameworks kept working—and now they’re thriving again because developers are rediscovering what they do well.

The future isn’t “SPA or server rendering.” It’s choosing the right tool for the actual problem you’re solving.

Start simpler. Add complexity only when you hit real limitations. Your users won’t notice the difference, but your team velocity will.


Frequently Asked Questions (FAQs)

Is server-side rendering fast enough for modern web apps?

Yes. With tools like Turbo, navigation feels instant. The perceived slowness of server rendering comes from full page refreshes, which modern approaches eliminate. I’ve shipped server-rendered apps that feel as responsive as SPAs to users.

What about mobile apps? Don’t you need React Native or similar?

For mobile apps, yes, you need a client-side framework. But most companies don’t need mobile apps. They need responsive web apps that work on mobile browsers. Server rendering handles that fine. Only build a native app when you need native APIs or offline functionality.

Can you scale server rendering to millions of users?

Absolutely. Rails scales to Shopify and GitHub. Laravel powers massive applications. The limiting factor is rarely the framework—it’s database design, caching strategy, and infrastructure. These matter equally for SPA backends.

What if we’ve already built an SPA? Should we rewrite it?

Probably not. Rewrites are expensive and risky. But for new features, consider building them server-rendered if they don’t need complex client state. You can run both approaches in the same codebase. Gradually shift new development toward simpler patterns.

What about team expertise? Our developers know React.

Valid concern. Don’t force a rewrite if your team is React-native. But for new projects, evaluate honestly: does this need React’s complexity, or are we just using it because it’s familiar? Familiarity is a reasonable tiebreaker, not a decision-maker.


Your turn: Look at the last project you built with React. How many pages genuinely needed client-side routing and state management? Would a server-rendered approach with targeted JavaScript have shipped faster?

Enjoying this? 👉 Tip a coffee and keep posts coming

Found this helpful?

Share it with your network

Jay McBride

Written by Jay McBride

Software engineer with 10+ years building production systems and mentoring developers. I write about the tradeoffs nobody mentions, the decisions that break at scale, and what actually matters when you ship. If you've already seen the AI summaries, you're in the right place.

Based on 10+ years building production systems and mentoring developers.

Support my work on Buy Me a Coffee

Recommended for You

8 min read

Angular Doesn't Suck: Debunking Myths and Proving Its Worth

Why Modern Angular Is Perfect for Enterprise and Large-Scale Applications

Read Article
5 min read

Angular Doesn’t Suck: Debunking Myths and Proving Its Worth

Why Modern Angular Is Perfect for Enterprise and Large-Scale Applications

Read Article