KahWee - Web Development, AI Tools & Tech Trends

Expert takes on AI tools like Claude and Sora, modern web development with React and Vite, and tech trends. By KahWee.

Migrating from Remix to React Router v7 - Part 1

Last weekend, I made the decision to migrate one of my full-stack React applications from Remix to React Router v7 framework mode. The migration took about two days and went surprisingly smooth. This is Part 1 covering why I made the switch and what challenged me. Part 2 covers the step-by-step process.

Why React Router v7?

React Router v7 is Remix v3 renamed. Ryan Florence and Michael Jackson merged the projects because "Remix v2 had become such a thin wrapper around React Router that an artificial separation developed between the two projects."

The practical benefits are immediate: instead of juggling @remix-run/node, @remix-run/react, @remix-run/serve, and others, everything consolidates into the unified react-router package. My dependencies dropped from 16 to 3. The new react-router typegen command eliminates manual type annotations in loader functions. The Vite-based build system is cleaner than Remix's custom setup.

The Toughest Part: TypeScript

While the API compatibility made most changes straightforward, TypeScript integration was by far the most challenging aspect of the migration. The git log reveals multiple commits dedicated to resolving type issues:

  • LoaderFunctionArgs and useLoaderData typing: Every route needed manual type updates
  • Route module type safety: New type patterns required learning React Router v7's approach
  • Interface mismatches: Database query results needed type alignment with component expectations
  • Case-sensitivity bugs: Database queries failed due to case-sensitive matching that worked in Remix

Multiple commits were dedicated to resolving TypeScript errors, indicating hours of type debugging across the migration. This taught me that React Router v7's type system is more strict than Remix v2, which is ultimately beneficial but creates friction during migration.

What Actually Changed

The migration touched 40 files with 327 insertions and 918 deletions - a net reduction of 591 lines. The main changes were package dependencies (replacing all @remix-run/* packages with React Router equivalents), build scripts (changed to use react-router CLI), and Vite configuration (simplified from complex Remix config with future flags to minimal React Router setup).

The Vite config transformation was dramatic:

- import { vitePlugin as remix } from '@remix-run/dev';
- // Complex remix configuration with future flags
- remix({
-   future: {
-     v3_fetcherPersist: true,
-     v3_relativeSplatPath: true,
-     v3_throwAbortReason: true,
-     v3_singleFetch: true,
-     v3_lazyRouteDiscovery: true,
-   }
- })
+ import { reactRouter } from '@react-router/dev/vite';
+ // Simple, clean configuration
+ plugins: [reactRouter(), tsconfigPaths(), tailwindcss()]

Every route and component needed import updates:

- import type { LoaderFunctionArgs } from '@remix-run/node';
- import { useLoaderData } from '@remix-run/react';
+ import type { LoaderFunctionArgs } from 'react-router';
+ import { useLoaderData } from 'react-router';

The API compatibility made most changes straightforward - loaders, actions, and components work identically. The migration was primarily changing imports and build configuration.

Some gotchas: Route files with .ts extensions needed to become .tsx for React Router v7 to recognize them. The entry.server.tsx got much simpler - React Router v7 removed the bot/browser request splitting logic. Bundle size dropped ~30% and build times improved with the unified package structure.

Continue to Part 2 for the detailed step-by-step migration process and understanding React Router's powerful typegen feature.