How React Router Integrates with Framework Tooling: The Complete Guide to @react-router/dev

@react-router/dev is the framework-mode integration layer that plugs React Router into Vite, enabling zero-config SSR, automatic code-splitting, HMR, and build-time manifest generation.

React Router's evolution into a framework-ready library relies on @react-router/dev, the official integration package that bridges the router with modern bundlers. This tooling layer converts a standard React Router application into a full-stack framework with server-side rendering, type-safe routes, and optimized production builds. Understanding how React Router framework tooling works is essential for developers building production-grade applications with the latest React Router architecture.

What Is @react-router/dev?

@react-router/dev exports a Vite plugin (reactRouterVitePlugin) that transforms a React Router application into a full-stack framework. Unlike traditional React Router usage where you manually set up a bundler and server, this package handles the entire build lifecycle, from development server configuration to production bundle optimization.

The plugin operates as a bridge between React Router's data router API and Vite's build system, injecting virtual modules, managing route manifests, and coordinating server-side rendering without requiring manual configuration.

How the Vite Plugin Bridges React Router and Bundlers

The integration works through eight distinct phases, each handled by specific modules within packages/react-router-dev.

Configuration Loading and Route Resolution

The plugin first detects and loads the React Router configuration using createConfigLoader in config/config.ts. This reads react-router.config.ts (or defaults to routes.ts in app/) and resolves all route files into a manifest structure that the build system can consume.

Virtual Module Generation

In vite/virtual-module.ts, the plugin creates a virtual Vite module graph. These virtual modules—including server-build, browser-manifest, and hmr-runtime—are injected into the Vite build but do not exist as physical files on disk. This allows the plugin to dynamically expose route manifests and build metadata to both client and server code.

Build-Time Manifest Generation

After Vite emits its own manifest, generateReactRouterManifestsForBuild (located in vite/plugin.ts around lines 569-627) walks the manifest to extract CSS and route chunk assets. It writes a browser manifest (exposed as window.__reactRouterManifest) and a server manifest used by the SSR entry to map URLs to route modules.

Route Module Code Splitting

The detectRouteChunksIfEnabled function in vite/route-chunks.ts identifies "route chunks"—specifically clientAction, clientLoader, clientMiddleware, and HydrateFallback exports. The plugin rewrites imports so these become separate lazy-loaded bundles, reducing the initial JavaScript payload while maintaining framework behavior.

Hot Module Replacement (HMR)

During development, the plugin sets up a Vite dev server child compiler that watches route source files. Using es-module-lexer, it re-parses exports and triggers Vite’s HMR runtime to replace route modules without full page reloads. This logic is handled in vite/plugin.ts around line 900.

Server-Side Bundle Generation

The plugin generates a server entry that imports every route module (or a stub when SPA mode is enabled) and exports the router configuration (routes, entry, prerender, etc.). This server bundle is built in a separate Vite environment (ssr), optionally using the new Vite Environment API when the v8_viteEnvironmentApi future flag is enabled. See getServerEntry in vite/plugin.ts (lines 892-925).

Type Generation

The built-in typegen watcher runs during vite serve to produce react-router-dev.d.ts that mirrors route exports (loader, action, params, etc.). This provides full TypeScript intellisense for route modules without manual type definitions. The implementation resides in typegen/index.ts.

Configuration Examples

Minimal Vite Configuration

To enable React Router framework tooling, add the plugin to your Vite configuration:

// vite.config.js
import { defineConfig } from 'vite';
import { reactRouterVitePlugin } from '@react-router/dev/vite/plugin';

export default defineConfig({
  plugins: [reactRouterVitePlugin()],
});

Behind the scenes, Vite calls reactRouterVitePlugin(), which registers hooks (config, configResolved, configureServer, closeBundle) that implement the integration steps detailed above.

Typical Project Layout

Framework mode expects a specific directory structure:


my-app/
├─ app/
│  ├─ routes/
│  │  ├─ _index.tsx          // → “/”
│  │  ├─ about.tsx           // → “/about”
│  │  └─ blog.$slug.tsx      // → “/blog/:slug”
│  └─ routes.ts              // (generated by flatRoutes)
├─ vite.config.js
└─ package.json

The routes.ts file is usually created with flatRoutes() from @react-router/fs-routes. The dev plugin reads this file, builds the route manifest, and injects the virtual module IDs required for SSR.

Custom Server Entry (Advanced)

For custom server implementations, you can access the development manifest directly:

// server/entry.server.ts
import { createRequestHandler } from 'react-router';
import { getReactRouterManifestForDev } from '@react-router/dev/vite/plugin';

export const requestHandler = async (req: Request) => {
  const manifest = await getReactRouterManifestForDev();
  return createRequestHandler({ request: req, manifest });
};

During production builds, the plugin writes a static manifest file (manifest-XXXXX.js) that the server imports directly, eliminating the need for runtime round-trips.

Experimental Environment API

To enable multiple SSR environments (e.g., separate bundles for Node.js and Cloudflare):

// vite.config.js
import { defineConfig } from 'vite';
import { reactRouterVitePlugin } from '@react-router/dev/vite/plugin';

export default defineConfig({
  plugins: [reactRouterVitePlugin()],
  reactRouter: {
    future: {
      v8_viteEnvironmentApi: true,
    },
  },
});

This activates the Vite Environment API, allowing you to define multiple environments in reactRouter.config.ts that build in parallel with isolated server bundles and manifests.

Key Source Files in @react-router/dev

File Role Link
vite/plugin.ts Core Vite plugin implementation (reactRouterVitePlugin) View
vite/virtual-module.ts Creates virtual modules exposing manifest and HMR runtime View
vite/route-chunks.ts Detects and builds separate route-chunk bundles View
config/config.ts Loads and validates React Router configuration View
typegen/index.ts Generates TypeScript types for route exports View
vite/plugins/validate-plugin-order.ts Ensures correct plugin ordering in Vite config View
vite/plugins/prerender.ts Implements static site generation via prerender hook View

These files collectively enable @react-router/dev to bridge React Router’s data-router API with Vite’s build lifecycle, delivering a seamless full-stack development experience.

Summary

  • @react-router/dev provides the reactRouterVitePlugin that transforms React Router into a full-stack framework via Vite integration.
  • The plugin handles configuration loading (config/config.ts), virtual module generation (vite/virtual-module.ts), and build-time manifest creation (vite/plugin.ts).
  • Route-level code splitting is automatic for clientAction, clientLoader, and HydrateFallback exports via vite/route-chunks.ts.
  • Hot Module Replacement works at the route level without full page reloads, and type generation provides full TypeScript intellisense for route exports.
  • The plugin supports multiple SSR environments through the experimental Vite Environment API (v8_viteEnvironmentApi).

Frequently Asked Questions

How do I add React Router framework tooling to an existing Vite project?

Install @react-router/dev and add the plugin to your vite.config.js. Import reactRouterVitePlugin from @react-router/dev/vite/plugin and include it in the plugins array. Ensure your project has the standard app/routes directory structure or configure custom paths in react-router.config.ts.

What is the difference between React Router library mode and framework mode?

Library mode requires you to manually configure your bundler and server, using createBrowserRouter or similar APIs directly. Framework mode, enabled by @react-router/dev, provides zero-config SSR, automatic code-splitting, type-safe routes, and built-in HMR by integrating deeply with Vite's build lifecycle.

How does route-level code splitting work in @react-router/dev?

The plugin analyzes route modules using detectRouteChunksIfEnabled in vite/route-chunks.ts to identify exports like clientAction, clientLoader, clientMiddleware, and HydrateFallback. It then rewrites imports so these exports become separate lazy-loaded bundles, reducing initial JavaScript payload while maintaining framework behavior.

Can I use @react-router/dev with custom server environments like Cloudflare Workers?

Yes. Enable the experimental v8_viteEnvironmentApi future flag in your Vite config to use the Vite Environment API. This allows you to define multiple SSR environments (e.g., Node.js, Cloudflare, Deno) in reactRouter.config.ts, each generating isolated server bundles and manifests during the build process.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →