Framework Mode
Framework Mode is the full-featured way to use React Router. It wraps Data Mode with a Vite plugin to add type-safe routing, intelligent code splitting, and flexible rendering strategies (SPA, SSR, SSG).
Quick Start
npx create-react-router@latest my-app
cd my-app
npm install
npm run dev
Visit http://localhost:5173 to see your app running.
What You Get
Framework Mode provides:
Type Safety : Auto-generated types for params, loader data, and actions
Code Splitting : Automatic route-based code splitting
SSR/SPA/SSG : Choose your rendering strategy per route
File-based or Config-based Routing : Use what works for your team
Optimized Builds : Production-ready bundling with Vite
Development Server : Fast HMR and instant feedback
Project Structure
A typical Framework Mode project looks like this:
my-app/
├── app/
│ ├── routes/
│ │ ├── _index.tsx # Home page (/)
│ │ ├── about.tsx # About page (/about)
│ │ └── products.$id.tsx # Product detail (/products/:id)
│ ├── root.tsx # Root layout
│ └── routes.ts # Route configuration
├── public/ # Static assets
├── react-router.config.ts # React Router config
└── vite.config.ts # Vite config
Configuring Routes
File-based (Recommended)
Config-based
Hybrid
Use the file system to define your routes with flatRoutes(): import { type RouteConfig } from "@react-router/dev/routes" ;
import { flatRoutes } from "@react-router/fs-routes" ;
export default flatRoutes () satisfies RouteConfig ;
File naming conventions: app/routes/
├── _index.tsx → /
├── about.tsx → /about
├── blog.$slug.tsx → /blog/:slug
├── settings.profile.tsx → /settings/profile
└── _layout.tsx → pathless layout route
Manually configure routes using helper functions: import {
type RouteConfig ,
route ,
index ,
layout ,
prefix ,
} from "@react-router/dev/routes" ;
export default [
index ( "./home.tsx" ),
route ( "about" , "./about.tsx" ),
layout ( "./auth/layout.tsx" , [
route ( "login" , "./auth/login.tsx" ),
route ( "register" , "./auth/register.tsx" ),
]),
... prefix ( "concerts" , [
index ( "./concerts/home.tsx" ),
route ( ":city" , "./concerts/city.tsx" ),
route ( "trending" , "./concerts/trending.tsx" ),
]),
] satisfies RouteConfig ;
Combine both approaches: import { type RouteConfig , route } from "@react-router/dev/routes" ;
import { flatRoutes } from "@react-router/fs-routes" ;
export default [
route ( "/" , "./home.tsx" ),
... ( await flatRoutes ()),
] satisfies RouteConfig ;
Route Modules
Route modules define behavior for each route using exports:
Basic
With Action
Full Example
// app/routes/product.$id.tsx
import type { Route } from "./+types/product.$id" ;
export async function loader ({ params } : Route . LoaderArgs ) {
const product = await fetchProduct ( params . id );
return { product };
}
export default function Product ({ loaderData } : Route . ComponentProps ) {
return (
< div >
< h1 > { loaderData . product . name } </ h1 >
< p > { loaderData . product . description } </ p >
</ div >
);
}
Type Safety
Framework Mode automatically generates types for your routes:
import type { Route } from "./+types/product.$id" ;
// ^
// Auto-generated from route file name
export async function loader ({ params } : Route . LoaderArgs ) {
// ^
// params.id is type-safe!
const product = await fetchProduct ( params . id );
return { product };
}
export default function Product ({ loaderData } : Route . ComponentProps ) {
// ^
// loaderData.product is type-safe!
return < h1 > { loaderData . product . name } </ h1 > ;
}
Types are generated automatically on file changes during development. No manual type definitions needed!
Nested Routes and Layouts
import { Outlet } from "react-router" ;
export default function DashboardLayout () {
return (
< div >
< aside >
< nav > { /* sidebar navigation */ } </ nav >
</ aside >
< main >
< Outlet /> { /* Child routes render here */ }
</ main >
</ div >
);
}
import { route , index } from "@react-router/dev/routes" ;
export default [
route ( "dashboard" , "./dashboard.tsx" , [
index ( "./dashboard/home.tsx" ), // /dashboard
route ( "settings" , "./dashboard/settings.tsx" ), // /dashboard/settings
route ( "profile" , "./dashboard/profile.tsx" ), // /dashboard/profile
]),
] ;
Data Loading Strategies
Server (SSR)
Client Only
Hybrid
// Runs on server for SSR, and on client for navigation
export async function loader ({ params } : Route . LoaderArgs ) {
const data = await db . getData ( params . id );
return { data };
}
// Only runs in the browser
export async function clientLoader ({ params } : Route . ClientLoaderArgs ) {
const res = await fetch ( `/api/data/ ${ params . id } ` );
return res . json ();
}
export function HydrateFallback () {
return < div > Loading... </ div > ;
}
// Server loader for SSR
export async function loader ({ params } : Route . LoaderArgs ) {
return await db . getData ( params . id );
}
// Client loader for SPA navigation
export async function clientLoader ({
params ,
serverLoader ,
} : Route . ClientLoaderArgs ) {
const serverData = await serverLoader ();
const clientData = await fetch ( `/api/extra/ ${ params . id } ` );
return { ... serverData , ... clientData };
}
Rendering Strategies
Configure in react-router.config.ts:
SPA
SSR
SSG (Pre-rendering)
export default {
ssr: false ,
} ;
Vite Plugin Setup
The plugin is configured in vite.config.ts:
import { reactRouter } from "@react-router/dev/vite" ;
import { defineConfig } from "vite" ;
export default defineConfig ({
plugins: [ reactRouter ()] ,
}) ;
Next Steps
Explore all the exports available in route modules: loader, action, meta, headers, ErrorBoundary, and more.
Implement protected routes with loaders and redirects.
Deploy to Vercel, Cloudflare, or any Node.js host with adapters.
Framework Mode gives you the full power of React Router with the best developer experience.