· 10 Min read

Remix 3: Why Ditching React Unlocks Real Web Dev

Remix 3: Why Ditching React Unlocks Real Web Dev

Link to section: BackgroundBackground

In May 2025, the Remix team announced they were waking up the project and moving away from React. That statement landed like a grenade in a framework community used to incremental upgrades. By January 2026, at Remix Jam 2025, the first real peek at Remix 3's architecture made clear this wasn't a small tweak. They'd built their own component model, event system, and state management from scratch.

This matters because Remix v1 and v2 were, functionally, thin wrappers around React Router. The team had become a maintenance burden managing both projects. So they consolidated React Router v7 as the stable base for React-first developers, and freed themselves to build what comes next.

What comes next is unusual: a full-stack framework that runs on web APIs alone, with zero React in the runtime.

Link to section: What Changed: The Core BreakdownWhat Changed: The Core Breakdown

Remix 3 isn't a version bump. It's a rethinking of what a web framework should be if you start fresh, knowing what we know now about JavaScript, the DOM, and developer experience.

On the frontend, Remix 3 uses JSX syntax but ditches the React runtime entirely. Components now use a this context object and explicit this.update() calls instead of hooks and reactive state. This feels foreign at first, but it maps directly to how the browser works: events fire, you handle them, you update the DOM. No virtual diff layer in between.

function Counter(this: Remix.Handle) {
  let count = 0;
  
  return () => (
    <button
      on={{
        click: () => {
          count++;
          this.update();
        }
      }}
    >
      Count: {count}
    </button>
  );
}

Compare that to modern React:

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

The Remix version is more explicit. There's no magic dependency array to forget. There's no hook call order rule. You call this.update() when you need the DOM to reflect your changes. That's it.

On the backend, Remix doubles down on web APIs. Handlers receive a standard Request object and return a Response. No custom Remix abstractions. This means the same code runs on Node.js, Deno, Bun, Cloudflare Workers, or any runtime that supports the Fetch API.

import { createRoot, route } from "@remix-run/core";

let routes = route({
  home: "/",
  about: "/about",
  books: {
    index: "/books",
    show: "/books/:slug",
  },
});

export const router = routes;

Routing is now TypeScript-defined, not file-based. The framework generates types for each route automatically, so a bad link or param name fails at compile time, not at runtime in production.

Link to section: Frontend Architecture: Signals, Events, and FramesFrontend Architecture: Signals, Events, and Frames

Remix 3's frontend layer rests on three pillars: signals for reactive data, web events as the communication layer, and a Frame component for async boundaries.

Signals are straightforward. You create a mutable value, and when it changes, you call this.update(). The DOM reconciliation happens with a hybrid approach: instead of virtual DOM diffing, Remix uses HTML-over-the-wire with intelligent morphing. When a Frame loads new HTML from the server, Remix morphs the old DOM to the new state, preserving focus and scroll position without a page reload.

Events use the browser's native event system. Instead of synthetic events, you bind with an on prop that maps to the DOM's event listeners.

import { createInteraction } from "@remix-run/dom";

let bpmTempo = createInteraction((event) => {
  // Calculate BPM from tap timing
  return tappedBPM;
});

function BPMTapper(this: Remix.Handle) {
  let bpm = 60;
  
  return () => (
    <button
      on={bpmTempo((event) => {
        bpm = event.detail;
        this.update();
      })}
    >
      BPM: {bpm}
    </button>
  );
}

The Frame component handles async loading boundaries. Think of it as an iframe without the iframe. You request data from the server, the server streams back HTML, and Remix morphs it into place. Hydrated components inside the Frame stay interactive while async content loads.

Link to section: The Real Performance Win: Reduced ComplexityThe Real Performance Win: Reduced Complexity

Smaller bundle sizes and faster TTI matter, but the real win is cognitive load. When you read Remix 3 code, you're reading imperative JavaScript that maps 1:1 to what the browser does. No hooks. No dependency arrays. No "keys" for lists. No re-render waterfalls.

Remix 3 code flow with explicit updates compared to React hooks

That matters most in larger apps. A 5000-line React component has hidden state updates scattered across three custom hooks and four useEffect blocks. A 5000-line Remix 3 component has explicit event handlers and this.update() calls that show you exactly when the DOM changes.

For AI, that's huge. AI code generation tools like Cursor perform worse when the code is implicit and magic-heavy. Remix 3's explicit model makes it easier for LLMs to reason about what will happen, which means fewer failed code generations.

Link to section: Server Runtime: Web Platform as FoundationServer Runtime: Web Platform as Foundation

Remix 3 treats the Fetch API and web standard libraries (FormData, File, Request/Response) as the canonical interface. This is radical in the Node.js world, where people reach for Express and raw Node APIs.

The tradeoff: you lose a bit of Node-specific power (like Node streams for backpressure), but you gain portability. Your Remix 3 app runs on Node 22, Deno, Bun, and edge runtimes without code changes.

The routing system generates TypeScript types for each route, catching broken links at compile time. That's not new, but the implementation here is simpler than Next.js App Router or SvelteKit because it doesn't have to manage React's server component rules or Svelte's reactivity system.

Link to section: Remix 3 vs Next.js vs Nuxt vs SvelteKitRemix 3 vs Next.js vs Nuxt vs SvelteKit

Here's how Remix 3 stacks up against the major full-stack alternatives:

CriterionRemix 3Next.js 15SvelteKitNuxt 4
Client runtimeCustom reactivityReact 19Svelte 5Vue 3
RSC supportHTML-over-wireYes, nativeNoNo
File-based routingNo (TS config)YesYesYes
Server APIsFetch APINode.js APIsNode.js APIsNode.js APIs
Type safetyFull route typesPartialPartialPartial
Startup time~50ms cold~150ms cold~60ms cold~100ms cold
Community sizeGrowingMassiveLargeLarge
Learning curveMediumHighLowMedium

Next.js is the obvious comparison. Both aim to be full-stack, both have API routes, both handle SSR. But Next.js locks you into React and Node.js. Remix 3 gives you web standards.

If your team knows React deeply and you're already on Vercel's infrastructure, stick with Next.js. The ecosystem is mature and you'll move faster in the short term. If you're building a new team or migrating from Rails/Django, Remix 3's explicit model and portability are worth the learning curve.

SvelteKit is faster to learn because Svelte itself is approachable. But Remix 3's lack of a magic reactivity system appeals to developers who've been bitten by Svelte's edge cases (like the $: reactive declaration gotchas). SvelteKit runs only on Node.js, making it less portable than Remix 3.

Nuxt works well if you're already in the Vue ecosystem and want a fast project bootstrap. For teams coming from Node.js and wanting to move down the stack, Remix 3's web API foundation feels more natural.

Link to section: Migration Path: From Next.js or React Router v7Migration Path: From Next.js or React Router v7

If you're on React Router v7, migration to Remix 3 is straightforward. Your server loader and action functions map nearly 1:1. The client side requires rewriting components from React hooks to Remix 3's explicit model, but the structure stays similar.

If you're on Next.js, the jump is bigger. You'll need to:

  1. Rewrite pages to route-based functions.
  2. Convert React components to use this.update() instead of useState.
  3. Move API routes to server action functions.
  4. Retrain the team on web standards instead of React patterns.

For a small app (under 5000 lines of component code), budget 2-3 weeks. For a large app (50k+ lines), budget 2-3 months and do it incrementally, one route at a time.

Remix offers a server-side gradual adoption story. You can run old and new routes side by side, migrating incrementally without a rewrite-everything moment.

Link to section: When NOT to Use Remix 3When NOT to Use Remix 3

Remix 3 ships with a built-in component library (buttons, forms, modals, etc.) but no ecosystem of third-party UI libraries yet. If you rely heavily on Material-UI, shadcn, or similar, you'll need to reimplement or build your own. That's not a blocker, but it's real work.

The developer community is also smaller than React or Vue. Finding developers and templates takes longer. The debugging story is still being built out. Hot module reloading works, but devtools integration lags what you get with Next.js on VS Code.

Also, Remix 3 is experimental. Michael Jackson and Ryan Florence (the creators) haven't committed to a release date beyond "as soon as possible." The packages are on npm and tested, but they're marked as unstable. Production use is possible but not recommended until a v3.0 stable release.

If you need a production-ready framework today, stick with Next.js, SvelteKit, or Nuxt. If you're prototyping or starting a new project with a team willing to live on the edge, Remix 3 is worth exploring.

Link to section: One Year Later: Will Remix 3 Matter?One Year Later: Will Remix 3 Matter?

The bigger question: will Remix 3 gain traction?

The honest answer is unclear. The React ecosystem is enormous. Next.js has the backing of Vercel, GitHub integration, and mindshare. For a new framework to succeed, it needs either a killer feature (Remix 3's web API portability is good but not killer) or a frustrated enough user base to migrate.

Remix v1 and v2 had a loyal following, but mostly among developers already tired of React's complexity. That's a niche, not a mass market.

The upside for Remix 3: if AI coding tools keep improving, explicit code becomes more valuable. A framework where state updates are obvious and event handling is straightforward might be easier for LLMs to generate and modify. In that world, Remix 3's design philosophy feels prescient.

The downside: the Node.js community moves slow. Even Node 22's native TypeScript support (via --experimental-strip-types) took years to land. Remix 3 could languish as an interesting experiment for five years before meaningful adoption.

Link to section: Setup and First StepsSetup and First Steps

If you want to try Remix 3, the packages are available on npm now:

npm install @remix-run/core @remix-run/dom @remix-run/node

Documentation is sparse (it's still experimental), but the source code is public on GitHub. The Remix Discord has early adopters willing to help.

Start with a simple counter or form, not a full app. Get a feel for this.update() and how it differs from React's render model. Then try a small server route that streams HTML back to a Frame component. Once that clicks, move to something bigger.

The migration friction is real, but for teams already maintaining custom React tooling or fighting with hook complexity, the payoff might be worth it. For everyone else, wait for the v3.0 stable release and see if the ecosystem grows.


The bottom line: Remix 3 is a genuine rethink of the full-stack JavaScript framework, but it's also a bet that explicit state management and web standards matter more than React's ecosystem. That might be true in 2027, or it might remain a cult classic used by 0.1% of JavaScript teams. Either way, it's worth watching.