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
98755ba
support react-router v8
Jun 22, 2026
55e290d
update comments
Jun 22, 2026
a4837a6
update lock file
Jun 22, 2026
ca7c03c
fix missing error boundary tests
Jun 23, 2026
c4e3f02
feat: Add ra-router-react-router-v8 adapter package
Jun 23, 2026
4f5c78a
docs: Note deferred unit tests pending React 19 test lane
Jun 23, 2026
cd80c84
refactor: Drop in-core react-router compat layer on react-router-v8-old
Jun 23, 2026
2817519
build: Keep react-router ^8.0.0 in the peer/dependency range
Jun 23, 2026
1b4448a
build: Remove unused react-router-dom from ra-ui-materialui
Jun 23, 2026
55b6684
fix: Restore canonical nested exports for ra-core and ra-ui-materialui
Jun 23, 2026
184460d
build: Exclude ESM-only ra-router-react-router-v8 from update-package…
Jun 23, 2026
c776b64
build: Drop redundant module/moduleResolution from v8 adapter tsconfig
Jun 23, 2026
2ac3add
feat: Extract react-router adapters into standalone packages
Jun 24, 2026
b8dbc16
refactor: Export reactRouterProvider from ra-core root only
Jun 24, 2026
e9d6522
docs: List reactRouterNextProvider in routing README components
Jun 24, 2026
2a29492
docs: Reference react-router instead of react-router-dom in routing R…
Jun 24, 2026
b5de8c1
feat: Support react-router v6 and v7 in the default adapter
Jun 24, 2026
5be0e85
test: Mirror ra-router-tanstack stories for ra-router-react-router-next
Jun 25, 2026
33b65a3
update react-router provider comments
Jun 25, 2026
5e6e4cd
test: Add unit tests for the React Router v8 adapter
Jun 25, 2026
3bb78be
test: Apply review feedback to React Router v8 adapter stories
Jun 25, 2026
13e52da
test: Mirror ra-router-tanstack stories and tests for ra-router-react…
Jun 25, 2026
3f05db9
test: Use built-in unsaved-changes guard in React Router v8 adapter s…
Jun 25, 2026
c06358a
test: Mirror routing stories and tests into ra-router-react-router
Jun 25, 2026
6b30b7a
test: Mirror ra-router-tanstack spec coverage in ra-router-react-rout…
Jun 25, 2026
e699a14
test: Mirror the comprehensive provider spec in ra-router-react-router
Jun 25, 2026
6c3209b
fix: Prepend basename in react-router-next Link/Navigate/useNavigate
Jun 25, 2026
d4702d0
fix: Prepend basename in react-router Link/Navigate/useNavigate
Jun 25, 2026
310b5af
test: Drop StandaloneWithBasename story from the router adapters
Jun 25, 2026
556cdf5
make path relative
Jun 25, 2026
bb272ac
refactor: Use tanstack's inline resolvePath for basename in router ad…
Jun 25, 2026
4b6db1d
refactor: Match tanstack's useNavigate resolvePath comments verbatim
Jun 25, 2026
bb2212f
refactor: Read basename directly in useNavigate like the tanstack ada…
Jun 26, 2026
af90e7e
refactor: Mirror tanstack useNavigate branch structure in router adap…
Jun 26, 2026
4f12274
refactor: Use tanstack's if/else resolvedTo shape in router adapter Link
Jun 26, 2026
5456d3e
refactor: Mirror tanstack Navigate string-building in router adapters
Jun 26, 2026
8e67456
refactor: Spread remaining Navigate props in router adapters
Jun 26, 2026
16a9661
refactor: Rename provider basename hook and align RouterWrapper context
Jun 26, 2026
c2fe8e4
test: Unify click mechanics with the tanstack spec (fireEvent.click)
Jun 26, 2026
df77b58
test: Align story import order with the tanstack spec
Jun 26, 2026
371b2ab
test: Restore user.click in the embedded navigate-back test (tanstack…
Jun 26, 2026
d63dc63
test: Restore tanstack's data-load comment in nested-routes test
Jun 26, 2026
9fe32a4
test: Tighten matchPath/useCanBlock parity with the tanstack spec
Jun 26, 2026
c9039fb
test: Clarify splat URL-decoding test description
Jun 26, 2026
0a7dd9d
test: Restore tanstack comment and reword URL-encoded params test
Jun 26, 2026
403fd90
test: Align useWarnWhenUnsavedChanges test descriptions with tanstack
Jun 26, 2026
f89c33c
test: Split block vs cancel in useWarnWhenUnsavedChanges tests
Jun 26, 2026
4f8c4f3
test: Drop inline comments from useWarnWhenUnsavedChanges tests
Jun 26, 2026
6358ef0
docs: Import Route components from react-router instead of react-rout…
Jun 26, 2026
8c56e0a
docs: Keep BrowserRouter on react-router-dom for v6 compatibility
Jun 26, 2026
450b853
fix: Scope experimental-vm-modules to the react-router-next test run
Jun 26, 2026
4ba858e
chore: Declare react-router as a dependency of ra-router-react-router…
Jun 26, 2026
6eaf342
chore: Drop ra-core from ra-router-react-router-next dev/peer deps
Jun 26, 2026
f4dcc93
chore: Fix router package peer dependencies
Jun 26, 2026
721c544
chore: Drop unused react-router-dom devDependency from react-admin
Jun 26, 2026
62eb94e
chore: Relock after router package dependency changes
Jun 26, 2026
aa11416
test: Harden auth e2e assertions against redirect timing flakiness
Jun 26, 2026
e2fd73d
chore: Align router package versions and relock after rebase onto next
Sep 8, 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