Why I Switched Away from React (And Don't Miss It)

by Daniel Reeves
Why I Switched Away from React (And Don't Miss It)

React didn't fail me dramatically. There was no single catastrophic bug, no project-ending meltdown. It was more like a slow accumulation of friction — the kind you stop noticing until you work in something else for a week and realize how much energy you were spending just to stay afloat.

I'd been writing React since 2017. Hooks arrived and I genuinely liked them at first. But by late 2023, after shipping three mid-sized apps with React 18 and wrestling with concurrent rendering edge cases, I started asking a question I'd been avoiding: is this tool actually making me faster, or am I just fluent in its particular brand of complexity?

This isn't a post telling you React is dead. It isn't. But here's an honest account of why I switched away from React, what I switched to, and what I'd tell a developer standing at the same crossroads.

The Dependency Graph That Ate My Mornings

Every React project I've touched in the last two years starts the same way: npx create-react-app (or Vite, now), then a cascade of decisions. Do we need Zustand or Redux Toolkit? React Query or SWR? React Router 6 or TanStack Router? And that's before we've written a single component.

The ecosystem fragmentation isn't new, but it's gotten worse. React itself is intentionally a UI layer — which sounds principled until you realize that means you're assembling a framework from twelve different authors who have different release schedules, different philosophies, and different ideas about breaking changes.

In one project, upgrading React Router from 6.8 to 6.11 quietly changed how loader functions handled redirects. We caught it in QA, not in tests, because the behavior change wasn't an error — it just silently did something different. That's the kind of thing that costs half a day.

I'm not blaming the React Router team. I'm saying the surface area of a modern React app is enormous, and most of that surface area lives outside React itself.

useEffect Is a Lie We Tell Ourselves

I'll say it plainly: useEffect is the most misused hook in the ecosystem, and the documentation — even the improved 2023 version — has not fully solved this.

The mental model requires you to think about synchronization, not events. That's correct and philosophically sound. It's also genuinely difficult to hold in your head when you're debugging why an effect fires twice in development (intentional in React 18 Strict Mode), or why a cleanup function isn't running when you expect, or why your WebSocket connection is being established three times.

Here's a real example I hit last year. We had a component that opened a WebSocket on mount:

useEffect(() => {
  const socket = new WebSocket(url);
  socket.onmessage = (event) => setMessages(prev => [...prev, event.data]);
  return () => socket.close();
}, [url]);

Clean, right? In production it was fine. In development with Strict Mode, the socket was opened, closed, and opened again — and the server we were connecting to treated the rapid reconnect as a suspicious client and throttled us. We spent four hours on a problem that was caused by the framework's development behavior differing from production.

You can argue this is fixable, that we should have handled reconnection logic properly. Sure. But the point is that React's own development tooling created a class of bugs that don't exist in production, which means they're invisible until someone notices the behavior in a demo.

The Bundle Size Conversation Nobody Wants to Have

React 18 + ReactDOM weighs in around 42 KB minified and gzipped. That's not catastrophic, but it's also not nothing — especially when you add React Router, a state library, and a component library on top.

For a content-heavy site serving users on mid-range Android devices in Southeast Asia or rural Europe, that baseline matters. I ran Lighthouse on a client's React app in late 2023 and the Time to Interactive on a throttled 4G connection was 6.2 seconds. After migrating to Astro with a few islands of interactivity, it dropped to 1.8 seconds. Same content, same hosting, same CDN.

That's not a marginal improvement. That's a different product.

What I Actually Switched To

I haven't landed on one tool. That's probably the honest answer most people don't give.

For content sites and marketing pages, I use Astro (currently on 4.x). It ships zero JavaScript by default and lets me drop in React, Svelte, or plain web components only where I need interactivity. The mental model is simple: HTML first, JavaScript only when necessary.

For app-like interfaces with real interactivity — dashboards, data-entry tools — I've been using Svelte (Svelte 4, with SvelteKit for routing and SSR). The reactivity model is compile-time, which means no virtual DOM diffing, no hook rules, and a significantly smaller runtime. The syntax feels like writing HTML with superpowers rather than JavaScript with JSX bolted on.

Here's the same WebSocket component in Svelte:

<script>
  import { onMount, onDestroy } from 'svelte';
  let messages = [];
  let socket;

  onMount(() => {
    socket = new WebSocket(url);
    socket.onmessage = (e) => messages = [...messages, e.data];
  });

  onDestroy(() => socket?.close());
</script>

onMount runs once, on mount. onDestroy runs on teardown. No Strict Mode double-invocation. No cleanup function inside a setup function. It does what it says.

Concern React 18 Svelte 4 Astro 4
Runtime size ~42 KB gz ~1.7 KB gz 0 KB (no JS by default)
Learning curve High (hooks, context, patterns) Medium Low–Medium
SSR story Next.js (separate framework) SvelteKit (built-in) Built-in
Ecosystem maturity Very high Growing Growing
Job market demand Very high Moderate Low

I'm not pretending the job market column doesn't matter. React skills are still worth money. If you're job-hunting, that's a real consideration and I won't wave it away.

What I Actually Miss About React

This section matters, because if I only complain, I'm being dishonest.

The ecosystem is genuinely unmatched. Need a drag-and-drop library? A rich text editor? A data grid that handles 100k rows? There are three React options for each, all actively maintained. In Svelte, you might find one option, or you'll be wrapping a vanilla JS library yourself.

The team familiarity argument is also real. If you're joining a team of ten React developers, introducing Svelte is a people problem, not just a technical one. I've been fortunate to work on projects where I had architectural latitude. Most developers don't.

And React's concurrent featuresuseTransition, useDeferredValue, Suspense for data fetching — are genuinely interesting ideas. I just think they're solving problems that a simpler architecture wouldn't have created in the first place.

Why I Switched Away from React: The Short Version

The accumulated cost of the ecosystem, the useEffect mental model, and the bundle weight finally exceeded the benefits for the kind of work I do — content sites, small-to-medium apps, performance-sensitive client projects. React optimizes for large teams building large apps. My work doesn't usually look like that.

If yours does — if you're on a team of fifteen engineers shipping a complex SPA — React probably still makes sense. The tooling, the hiring pool, the component libraries: they're all built around that use case.

But if you're a solo developer or a small agency building things that need to be fast and maintainable by non-React specialists, the calculus looks different. I'd encourage you to spend a weekend with SvelteKit or Astro before assuming React is the default answer.

Tomorrow's action: Pick one small project — a landing page, a personal tool, anything — and build it in Astro or SvelteKit instead of React. Not to replace React permanently, but to get a calibrated sense of what the friction difference actually feels like in your hands. That's the only benchmark that matters.