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 2

This is Part 2 covering the step-by-step migration process. Part 1 covered why I migrated and the main challenges.

The Step-by-Step Process

Phase 1: Dependencies and Configuration (30 minutes)

Replace all @remix-run/* packages with React Router equivalents in package.json. Change build/dev commands to use the react-router CLI. Simplify Vite configuration. Add react-router.config.ts for framework-specific settings.

Phase 2: Import Refactoring (90 minutes)

This phase ate the most time. Every file importing from @remix-run/* needed updates:

  • Route files (23 files): Updated loader/action imports and useLoaderData calls
  • Component files (8 files): Changed Link and Form imports
  • Entry files (2 files): Updated server/client entry points
  • Utility files (7 files): Modified auth and theme utilities

Start with entry points, then routes, then components. Fix TypeScript errors as they appear.

Phase 3: Configuration Fine-tuning (30 minutes)

Updated TypeScript config to use React Router types. Converted splat routes from api.auth.$.ts to api.auth.$rest.tsx. Added the react-router typegen command to dev scripts.

Typegen Changes Everything

Tip

react-router typegen automatically generates route-specific TypeScript types, eliminating manual interface definitions and preventing type drift.

This feature didn't exist in Remix. It's a React Router v7 innovation.

The typegen command generates a +types/<route-file>.d.ts for each route. You get automatic type inference for loader data, route-specific parameter typing, action and loader return type validation, and component prop type safety — all from your actual route implementation.

Instead of manually defining LoaderData interfaces for every route, typegen infers them from your loaders:

// New React Router v7 pattern - types inferred automatically
export async function loader({ request }: LoaderFunctionArgs) {
  // Return whatever you want, typegen handles the interface
  return { recipes: await getRecipes(), user: await getUser(request) };
}

// Component gets proper typing automatically
export default function RecipeList() {
  const { recipes, user } = useLoaderData<typeof loader>();
  // recipes and user are fully typed without manual interfaces
}

This fixes the interface drift problem. With manual interfaces, you change a loader to return additional data but forget to update the interface. TypeScript doesn't complain — it silently ignores the new properties.

useLoaderData<typeof loader> eliminates this. The type reflects your actual loader implementation. When you refactor a loader's return structure, every component using that data updates automatically.

What Happened to Remix?

Note

Remix v3 is a complete rewrite with no React dependencies—built on a Preact fork for "AI-first development." React Router v7 is now the clear successor for production React applications.

Ryan Florence and Michael Jackson handed React Router v7 to an open governance committee while they focus on Remix v3. Remix v3 isn't an iteration — it's a complete rewrite with no React dependencies. They're building on a fork of Preact for "AI-first development" and "AI driven user interfaces."

React Router v7 is the successor for production React apps that want the Remix experience.

Worth the Switch?

React Router v7 is actively developed. Remix v2 is in maintenance mode. The simplified dependency tree, better TypeScript integration, and improved build system pay off immediately.

For teams on Remix v2 with future flags enabled, the migration is straightforward. AI assistance was critical for batch import updates across 40+ files. What could have been a full day of manual work became two focused sessions.

The migration runs smoothly on React Router v7 now. Same power as Remix, simpler tooling, stronger types.