Choosing the right routing strategy is one of the first big decisions when building a React app. With the release of React Router v7, routing has evolved beyond simple navigation into a set of modes that can also manage data, handle mutations, and even power full-stack applications.
In this guide, we’ll break down the three modes React Router v7 offers: declarative, data, and framework and, explore when to use each. By the end, you’ll not only understand how these modes differ, but you’ll also know exactly which one fits your project’s needs.
React Router started as a routing library for Single-Page Applications (SPAs). In v4, the authors added support for routing React Native apps, which meant there were now two ways to use the library. To distinguish between these use cases, the team released two different libraries: react-router-dom
for Web Apps and react-router-native
for React Native.
React Router kept evolving, but maintained that distinction between DOM and React Native. However, at the same time, the React Router team was developing a full-stack React framework called Remix. Remix used React Router to manage its routing. The team also kept pushing the capabilities of the Router.
Eventually, in the v6 release, React Router became more than just a routing library. It also started to handle data loading and mutations in routes. Data handling patterns from Remix were ported to React Router as the authors thought those patterns would be beneficial to the routing library.
In v7, the React Router team decided to combine Remix and React Router, releasing them as just one library: react-router
. They did this mainly because the two libraries had grown very similar over time, and they wanted to free up the Remix brand for something more ambitious. React Router was transformed from a simple routing library to a full-stack framework.
Due to the multiple use cases of React Router v7, the team grouped its functionalities into different modes. This was to make it more intuitive for developers to adopt. This differentiation was also important to anyone migrating from Remix to React Router. It was clearer what the migration path looked like and what features were made for the full-stack framework.
Note: The v7 release also dropped support for React Native, so the library is now only imported as react-router
.
Modes | Declarative Mode | Data Mode | Framework Mode |
---|---|---|---|
Previous version | React Router v5/v6 | React Router v6.4+ | Remix v2 |
Server-side rendering | Not supported | Supported (with DIY setup) | Supported |
Typesafety | Not supported | Not supported | Supported |
Recommended use case | Simple routing | Routing and data handling | Full-stack development |
In the sections below, we’ll walk through each of the three React Router v7 modes: declarative, data, and framework. For each, we’ll cover what it does, its pros and cons, and the scenarios where it’s the best fit.
The declarative mode of React Router allows simple declarative routing for SPAs. With this mode, one can set up routes with JSX, use React Router’s built-in components like <Link>
and <NavLink>
, nest routes with <Outlet />
, and has access to hooks like useNavigate
and useLocation
.
This example illustrates how to create a simple index route in declarative mode:
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Routes, Route } from "react-router"; import App from "./app"; const root = document.getElementById("root"); ReactDOM.createRoot(root).render( } /> , );
Pros of using the declarative mode in React Router are:
Cons of using the declarative mode in React Router are:
<Form/>
component and its associated hooks and functionsChoose React Router’s declarative mode when:
Think of data mode in React Router as an extended declarative mode. While declarative mode is minimal, data mode gives access to more tools. This mode configures routes outside of React’s rendering, which allows React Router to perform services like data loading for routes, handling data mutations with actions, robust form management, and many more.
The example below shows how to create a simple route in data mode:
import { createBrowserRouter } from "react-router"; function Root() { return <h1>Hello world</h1>; } const router = createBrowserRouter([ { path: "/", Component: Root }, ]);
The Pros of using data mode are:
The cons of using data mode are:
Choose data mode when:
The framework mode of React Router is essentially a data mode as a packaged and polished full-stack React framework. Along with the benefits of data mode, framework mode adds enhancements like typesafety, straightforward config for SPAs, SSRs, or Static generation.
A major addition in this mode is file-based routing, which allows you to define routes directly from the file system rather than manually configuring them. This makes organizing routes more intuitive and helps scale larger applications.
Framework mode is also positioned as an alternative to other full-stack React frameworks like Next.js and TanStack Start.
Below is an example that shows how to define routes in framework mode:
// src/routes.ts 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;
The pros of framework mode are:
On the other hand, the cons of framework mode are:
Choose framework mode when:
React Router has grown from a simple client-side routing tool into a flexible framework with multiple modes. While the options in v7 can feel overwhelming at first, the key is matching the mode to your project’s needs: declarative for simple SPAs, data for apps that need routing plus data handling, and framework for full-stack development with type safety and SSR support.
With these distinctions in mind, you can approach new projects confidently, knowing which mode to reach for and why.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCSS @function brings reusable logic to native CSS. Learn how to replace Sass and JavaScript utilities with native functions for typography, color, and fluid design.
Discover a handy pattern for routing LLM calls in an “environment-aware” manner, using AI SDK’s middleware.
React Server Components aim to simplify data fetching and boost performance, but quirks in caching and loading often turn simple apps into debugging puzzles.
Build smarter frontend chatbots with RAG and LangChain.js. Learn how to add context, improve accuracy, and cut costs with a practical tutorial.