Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
9ef5fdc
support react-router v8
Jun 22, 2026
6c3b189
update comments
Jun 22, 2026
352bcd5
update lock file
Jun 22, 2026
0df7e82
fix missing error boundary tests
Jun 23, 2026
f515372
feat: Add ra-router-react-router-v8 adapter package
Jun 23, 2026
d8b41fc
docs: Note deferred unit tests pending React 19 test lane
Jun 23, 2026
cd163ee
Merge branch 'feature/react-router-v8' into react-router-v8-old
Jun 23, 2026
eb6032e
refactor: Drop in-core react-router compat layer on react-router-v8-old
Jun 23, 2026
e85b130
build: Keep react-router ^8.0.0 in the peer/dependency range
Jun 23, 2026
69955ef
build: Remove unused react-router-dom from ra-ui-materialui
Jun 23, 2026
802469e
fix: Restore canonical nested exports for ra-core and ra-ui-materialui
Jun 23, 2026
cb85c7d
build: Exclude ESM-only ra-router-react-router-v8 from update-package…
Jun 23, 2026
61e650d
build: Drop redundant module/moduleResolution from v8 adapter tsconfig
Jun 23, 2026
eb8cabf
feat: Extract react-router adapters into standalone packages
Jun 24, 2026
0868f54
refactor: Export reactRouterProvider from ra-core root only
Jun 24, 2026
060de5e
docs: List reactRouterNextProvider in routing README components
Jun 24, 2026
0cbc7ca
docs: Reference react-router instead of react-router-dom in routing R…
Jun 24, 2026
e1a62e4
feat: Support react-router v6 and v7 in the default adapter
Jun 24, 2026
ff4e528
test: Mirror ra-router-tanstack stories for ra-router-react-router-next
Jun 25, 2026
ba20638
update react-router provider comments
Jun 25, 2026
689e5dd
test: Add unit tests for the React Router v8 adapter
Jun 25, 2026
84ea0f5
test: Apply review feedback to React Router v8 adapter stories
Jun 25, 2026
f8f4b69
test: Mirror ra-router-tanstack stories and tests for ra-router-react…
Jun 25, 2026
fbd10e8
test: Use built-in unsaved-changes guard in React Router v8 adapter s…
Jun 25, 2026
4839c64
test: Mirror routing stories and tests into ra-router-react-router
Jun 25, 2026
2bbf948
test: Mirror ra-router-tanstack spec coverage in ra-router-react-rout…
Jun 25, 2026
aba3a03
test: Mirror the comprehensive provider spec in ra-router-react-router
Jun 25, 2026
3b1e367
fix: Prepend basename in react-router-next Link/Navigate/useNavigate
Jun 25, 2026
42ff222
fix: Prepend basename in react-router Link/Navigate/useNavigate
Jun 25, 2026
12d951c
test: Drop StandaloneWithBasename story from the router adapters
Jun 25, 2026
1345d92
make path relative
Jun 25, 2026
d35e958
refactor: Use tanstack's inline resolvePath for basename in router ad…
Jun 25, 2026
c4b0b0f
refactor: Match tanstack's useNavigate resolvePath comments verbatim
Jun 25, 2026
ffa03ca
refactor: Read basename directly in useNavigate like the tanstack ada…
Jun 26, 2026
095e887
refactor: Mirror tanstack useNavigate branch structure in router adap…
Jun 26, 2026
b726778
refactor: Use tanstack's if/else resolvedTo shape in router adapter Link
Jun 26, 2026
9ad07c5
refactor: Mirror tanstack Navigate string-building in router adapters
Jun 26, 2026
e021aa0
refactor: Spread remaining Navigate props in router adapters
Jun 26, 2026
4f36a84
refactor: Rename provider basename hook and align RouterWrapper context
Jun 26, 2026
6210820
test: Unify click mechanics with the tanstack spec (fireEvent.click)
Jun 26, 2026
87fed4c
test: Align story import order with the tanstack spec
Jun 26, 2026
5aad20a
test: Restore user.click in the embedded navigate-back test (tanstack…
Jun 26, 2026
341bcb4
test: Restore tanstack's data-load comment in nested-routes test
Jun 26, 2026
f714be7
test: Tighten matchPath/useCanBlock parity with the tanstack spec
Jun 26, 2026
3522062
test: Clarify splat URL-decoding test description
Jun 26, 2026
1c5b74e
test: Restore tanstack comment and reword URL-encoded params test
Jun 26, 2026
deee383
test: Align useWarnWhenUnsavedChanges test descriptions with tanstack
Jun 26, 2026
3a7206b
test: Split block vs cancel in useWarnWhenUnsavedChanges tests
Jun 26, 2026
bf8103e
test: Drop inline comments from useWarnWhenUnsavedChanges tests
Jun 26, 2026
5595607
docs: Import Route components from react-router instead of react-rout…
Jun 26, 2026
1fac4d3
docs: Keep BrowserRouter on react-router-dom for v6 compatibility
Jun 26, 2026
95b0fe8
fix: Scope experimental-vm-modules to the react-router-next test run
Jun 26, 2026
0cdb710
chore: Declare react-router as a dependency of ra-router-react-router…
Jun 26, 2026
6db8be0
chore: Drop ra-core from ra-router-react-router-next dev/peer deps
Jun 26, 2026
8d69dd7
chore: Fix router package peer dependencies
Jun 26, 2026
0473c75
chore: Drop unused react-router-dom devDependency from react-admin
Jun 26, 2026
77309a1
chore: Relock after router package dependency changes
Jun 26, 2026
6ccbaaa
test: Harden auth e2e assertions against redirect timing flakiness
Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ build-offline: ## build the offline example
preview-offline: ## preview the offline example
@yarn preview-offline

build-ra-router-react-router:
@echo "Transpiling ra-router-react-router files...";
@cd ./packages/ra-router-react-router && yarn build

build-ra-core:
@echo "Transpiling ra-core files...";
@cd ./packages/ra-core && yarn build
Expand All @@ -56,6 +60,10 @@ build-ra-router-tanstack:
@echo "Transpiling ra-router-tanstack files...";
@cd ./packages/ra-router-tanstack && yarn build

build-ra-router-react-router-next:
@echo "Transpiling ra-router-react-router-next files...";
@cd ./packages/ra-router-react-router-next && yarn build

build-ra-ui-materialui:
@echo "Transpiling ra-ui-materialui files...";
@cd ./packages/ra-ui-materialui && yarn build
Expand Down Expand Up @@ -129,7 +137,7 @@ update-package-exports: ## Update the package.json "exports" field for all packa
@echo "Updating package exports..."
@yarn tsx ./scripts/update-package-exports.ts

build: build-ra-core build-ra-router-tanstack build-ra-data-fakerest build-ra-ui-materialui build-ra-data-json-server build-ra-data-local-forage build-ra-data-local-storage build-ra-data-simple-rest build-ra-data-graphql build-ra-data-graphql-simple build-ra-input-rich-text build-data-generator build-ra-language-english build-ra-language-french build-ra-i18n-i18next build-ra-i18n-polyglot build-react-admin build-ra-no-code build-create-react-admin update-package-exports ## compile ES6 files to JS
build: build-ra-router-react-router build-ra-core build-ra-router-tanstack build-ra-router-react-router-next build-ra-data-fakerest build-ra-ui-materialui build-ra-data-json-server build-ra-data-local-forage build-ra-data-local-storage build-ra-data-simple-rest build-ra-data-graphql build-ra-data-graphql-simple build-ra-input-rich-text build-data-generator build-ra-language-english build-ra-language-french build-ra-i18n-i18next build-ra-i18n-polyglot build-react-admin build-ra-no-code build-create-react-admin update-package-exports ## compile ES6 files to JS

typecheck: ## check TypeScript types
@yarn typecheck
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/auth.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Authentication', () => {
it('should go to login page after logout', () => {
ListPage.navigate();
ListPage.logout();
cy.url().then(url => expect(url).to.contain('/#/login'));
cy.url().should('contain', '/#/login');
});

it('should redirect to login page when not logged in', () => {
Expand All @@ -28,7 +28,7 @@ describe('Authentication', () => {
ListPage.logout();
LoginPage.login('login', 'password');
ListPage.navigate();
cy.url().then(url => expect(url).to.contain('/#/posts'));
cy.url().should('contain', '/#/posts');
});

it('should redirect to initial url keeping query string', () => {
Expand All @@ -42,7 +42,7 @@ describe('Authentication', () => {
ListPage.setAsNonLogged();
cy.reload();
LoginPage.login('login', 'password');
cy.url().then(urlAfterLogin => {
cy.url().should(urlAfterLogin => {
expect(urlAfterLogin).to.contain(urlBeforeLogout);
});
ListPage.commentableFilter().should('exist');
Expand Down
18 changes: 11 additions & 7 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ In most apps, you need to pass more props to `<Admin>`. Here is a more complete
```tsx
// in src/App.js
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider, authProvider, i18nProvider } from './providers';
import { Layout } from './layout';
Expand Down Expand Up @@ -86,7 +86,7 @@ To make the main app component more concise, a good practice is to move the reso
```tsx
// in src/App.js
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider, authProvider, i18nProvider } from './providers';
import { Layout } from './layout';
Expand Down Expand Up @@ -396,7 +396,8 @@ The Auth Provider also lets you configure redirections after login/logout, anony
Use this prop to make all routes and links in your Admin relative to a "base" portion of the URL pathname that they all share. This is required when using the [`BrowserRouter`](https://reactrouter.com/en/main/router-components/browser-router) to serve the application under a sub-path of your domain (for example https://marmelab.com/ra-enterprise-demo), or when embedding react-admin inside a single-page app with its own routing.

```tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Routes, Route } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import { StoreFront } from './StoreFront';
import { StoreAdmin } from './StoreAdmin';

Expand Down Expand Up @@ -1158,7 +1159,7 @@ In addition to [`<Resource> elements`](./Resource.md) for CRUD pages, you can us
```tsx
// in src/App.js
import * as React from "react";
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
import { Admin, Resource, CustomRoutes } from 'react-admin';
import posts from './posts';
import comments from './comments';
Expand Down Expand Up @@ -1190,7 +1191,8 @@ By default, react-admin uses react-router with a [HashRouter](https://reactroute
But you may want to use another routing strategy, e.g. to allow server-side rendering of individual pages. React-router offers various Router components to implement such routing strategies. If you want to use a different router, simply put your app in a create router function. React-admin will detect that it's already inside a router, and skip its own router.
```tsx
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { RouterProvider } from 'react-router';
import { createBrowserRouter } from 'react-router-dom';
import { Admin, Resource } from 'react-admin';
import { dataProvider } from './dataProvider';

Expand All @@ -1217,7 +1219,8 @@ However, if you serve your admin from a sub path AND use another Router (like [`
```tsx
import { Admin, Resource } from 'react-admin';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { RouterProvider } from 'react-router';
import { createBrowserRouter } from 'react-router-dom';
import { dataProvider } from './dataProvider';

const App = () => {
Expand Down Expand Up @@ -1249,7 +1252,8 @@ If you want to use react-admin as a sub path of a larger React application, chec
You can include a react-admin app inside another app, using a react-router `<Route>`:
```tsx
import { RouterProvider, Routes, Route, createBrowserRouter } from 'react-router-dom';
import { RouterProvider, Routes, Route } from 'react-router';
import { createBrowserRouter } from 'react-router-dom';
import { StoreFront } from './StoreFront';
import { StoreAdmin } from './StoreAdmin';

Expand Down
2 changes: 1 addition & 1 deletion docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ For example, the following react-admin application:

```jsx
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

export const App = () => (
<Admin dataProvider={dataProvider}>
Expand Down
2 changes: 1 addition & 1 deletion docs/Authenticated.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use it as an alternative to the [`useAuthenticated()`](./useAuthenticated.md) ho

```jsx
import { Admin, CustomRoutes, Authenticated } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

const App = () => (
<Admin authProvider={authProvider}>
Expand Down
4 changes: 2 additions & 2 deletions docs/Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ When you add custom pages, they are accessible to anonymous users by default. To

```jsx
import { Admin, CustomRoutes, useAuthenticated } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

const RestrictedPage = () => {
const { isPending } = useAuthenticated(); // redirects to login if not authenticated
Expand Down Expand Up @@ -127,7 +127,7 @@ Alternatively, use the [`<Authenticated>` component](./Authenticated.md) to disp

```jsx
import { Admin, CustomRoutes, Authenticated } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

const RestrictedPage = () => (
<Authenticated>
Expand Down
4 changes: 2 additions & 2 deletions docs/Breadcrumb.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ Let's say that this custom page is added to the app under the `/settings` URL:
```jsx
// in src/App.jsx
import { Admin, Resource, CustomRoutes, } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

import { MyLayout } from './MyLayout';
import { UserPreferences } from './UserPreferences';
Expand Down Expand Up @@ -854,7 +854,7 @@ For instance, the screencast at the top of this page shows a `songs` resource ne

```jsx
import { Admin, Resource } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

export const App = () => (
<Admin dataProvider={dataProvider}>
Expand Down
2 changes: 1 addition & 1 deletion docs/CanAccess.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Use the [`<CustomRoutes>`](./CustomRoutes.md) component to add custom routes to

```tsx
import { Admin, CustomRoutes, Authenticated, CanAccess, AccessDenied, Layout } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

import { LogsPage } from './LogsPage';
import { MyMenu } from './MyMenu';
Expand Down
2 changes: 1 addition & 1 deletion docs/ContainerLayout.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ import {
ListGuesser,
EditGuesser,
} from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
import {
ContainerLayout,
HorizontalMenu,
Expand Down
14 changes: 7 additions & 7 deletions docs/CustomRoutes.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The `Route` element depends on the routing library you use (e.g. `react-router`

```jsx
// for react-router
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
// for tanstack-router
import { tanStackRouterProvider } from 'ra-router-tanstack';
const { Route } = tanStackRouterProvider;
Expand All @@ -62,7 +62,7 @@ Now, when a user browses to `/settings` or `/profile`, the components you define
```jsx
// in src/App.js
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider } from './dataProvider';
import { Settings } from './Settings';
Expand Down Expand Up @@ -95,7 +95,7 @@ Here is an example of application configuration mixing custom routes with and wi
```jsx
// in src/App.js
import { Admin, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider } from './dataProvider';
import { Register } from './Register';
Expand Down Expand Up @@ -124,7 +124,7 @@ By default, custom routes can be accessed even by anomymous users. If you want t
```jsx
// in src/App.js
import { Admin, CustomRoutes, Authenticated } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider } from './dataProvider';
import { Settings } from './Settings';
Expand Down Expand Up @@ -207,7 +207,7 @@ Finally, pass the custom `<Layout>` component to `<Admin>`:
```jsx
// in src/App.js
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider } from './dataProvider';
import { MyLayout } from './MyLayout';
Expand Down Expand Up @@ -276,7 +276,7 @@ To do so, add the `<Route>` elements as [children of the `<Resource>` element](.

```jsx
import { Admin, Resource } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

import { dataProvider } from './dataProvider';
import posts from './posts';
Expand Down Expand Up @@ -307,7 +307,7 @@ This is usually useful for nested resources, such as books on authors:
```jsx
// in src/App.jsx
import { Admin, Resource, ListGuesser, EditGuesser } from 'react-admin';
import { Route } from "react-router-dom";
import { Route } from "react-router";

const App = () => (
<Admin dataProvider={dataProvider}>
Expand Down
6 changes: 3 additions & 3 deletions docs/DeletedRecordsList.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ However, you need to define the route to reach this component manually using [`<
```tsx
// in src/App.js
import { Admin, CustomRoutes } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

export const App = () => (
Expand Down Expand Up @@ -116,7 +116,7 @@ However, you **must** use [`<ShowDeleted>`](./ShowDeleted.md) component instead
{% raw %}
```tsx
import { Admin, CustomRoutes, SimpleShowLayout, TextField } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
import { DeletedRecordsList, ShowDeleted } from '@react-admin/ra-soft-delete';

const ShowDeletedBook = () => (
Expand Down Expand Up @@ -394,7 +394,7 @@ In the example below, the deleted records lists store their list parameters sepa
{% raw %}
```tsx
import { Admin, CustomRoutes } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

const Admin = () => {
Expand Down
2 changes: 1 addition & 1 deletion docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ import {
List,
DataTable,
} from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

const NewerBooks = () => (
<List
Expand Down
2 changes: 1 addition & 1 deletion docs/Permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ Use the [`<CustomRoutes>`](./CustomRoutes.md) component to add custom routes to

```tsx
import { Admin, CustomRoutes, Authenticated, CanAccess, AccessDenied, Layout } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

import { LogsPage } from './LogsPage';
import { MyMenu } from './MyMenu';
Expand Down
59 changes: 59 additions & 0 deletions docs/ReactRouterV8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
layout: default
title: "React Router v8 Integration"
---

# React Router v8 Integration

By default, react-admin is powered by [React Router](https://reactrouter.com/) v6/v7 through the `ra-router-react-router` adapter, which is installed automatically. React Router v8 merged the former `react-router-dom` package into `react-router` and requires **React 19**, so support for it ships as a separate, opt-in adapter package: `ra-router-react-router-next`.

**New projects are encouraged to run on React Router v8 using `ra-router-react-router-next`.** The React Router v6/v7 adapter remains the default for backward compatibility, and **`ra-router-react-router-next` will become the default `ra-router-react-router` in react-admin v6.**

Use this package when your application runs on React Router v8.

## Installation

```bash
npm install ra-router-react-router-next react-router@^8
# or
yarn add ra-router-react-router-next react-router@^8
```

React Router v8 requires React 19. Make sure your application uses `react@^19.2.7` and `react-dom@^19.2.7`.

## Configuration

Set the `<Admin routerProvider>` to `reactRouterNextProvider`:

```tsx
import { Admin, Resource, ListGuesser } from 'react-admin';
import { reactRouterNextProvider } from 'ra-router-react-router-next';
import { dataProvider } from './dataProvider';

const App = () => (
<Admin
dataProvider={dataProvider}
routerProvider={reactRouterNextProvider}
>
<Resource name="posts" list={ListGuesser} />
<Resource name="comments" list={ListGuesser} />
</Admin>
);

export default App;
```

That's it! React-admin will now use React Router v8 for all routing operations.

## Standalone Mode

When using `reactRouterNextProvider` without an existing React Router, react-admin creates its own hash router automatically (URLs like `/#/posts`). This is called **standalone mode**. This is the simplest setup and requires no additional configuration.

## Embedded Mode

If your application already uses React Router, you can embed react-admin inside it. React-admin detects the existing router context and uses it instead of creating its own.

## When To Use This Package

- Use the **default** `ra-router-react-router` adapter (installed automatically, no extra setup) if your app runs on React Router v6 or v7.
- Use **`ra-router-react-router-next`** if your app runs on React Router v8 (and therefore React 19). This is the recommended choice for new projects, and it will become the default in react-admin v6.
4 changes: 2 additions & 2 deletions docs/Resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ For instance, the following code creates an `authors` resource, and adds an `/au
```jsx
// in src/App.jsx
import { Admin, Resource } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

import { AuthorList } from './AuthorList';
import { BookList } from './BookList';
Expand Down Expand Up @@ -329,7 +329,7 @@ React-admin doesn't support nested resources, but you can use [the `children` pr

```jsx
import { Admin, Resource } from 'react-admin';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';

export const App = () => (
<Admin dataProvider={dataProvider}>
Expand Down
Loading
Loading