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

I migrated a full-stack React application from Remix to React Router v7 framework mode over a weekend. Two days, surprisingly smooth. This is Part 1 covering why I switched and what tripped me up. 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 payoff is immediate. Instead of juggling @remix-run/node, @remix-run/react, @remix-run/serve, and others, everything consolidates into react-router. My dependencies dropped from 16 to 3. The react-router typegen command eliminates manual type annotations in loader functions. The Vite-based build system is cleaner than Remix's custom setup.

TypeScript Was the Hard Part

The API stayed compatible. TypeScript did not.

The git log tells the story — 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

Important

Multiple commits were dedicated to resolving TypeScript errors, indicating hours of type debugging across the migration. 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: package dependencies (replacing all @remix-run/* packages), build scripts (switched to react-router CLI), and Vite configuration.

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';

Loaders, actions, and components work identically. The migration was mostly changing imports and build configuration.

Tip

Route files with .ts extensions need to become .tsx for React Router v7 to recognize them.

The entry.server.tsx got 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 React Router's typegen feature.