TanStack Router Integration

React-admin supports TanStack Router as an alternative to react-router. This allows you to use react-admin in a TanStack Start application.

Installation

To use TanStack Router with react-admin, install the required packages:

npm install ra-router-tanstack @tanstack/react-router @tanstack/history
# or
yarn add ra-router-tanstack @tanstack/react-router @tanstack/history

Configuration

To use TanStack Router, set the <Admin routerProvider> to tanStackRouterProvider:

import { Admin, Resource, ListGuesser } from 'react-admin';
import { tanStackRouterProvider } from 'ra-router-tanstack';
import { dataProvider } from './dataProvider';

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

export default App;

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

Standalone Mode

When using tanStackRouterProvider without an existing TanStack Router, react-admin creates its own router automatically. This is called standalone mode.

In standalone mode, react-admin:

  • Creates a TanStack Router with hash-based history (URLs like /#/posts)
  • Handles all route matching internally
  • Manages navigation and history

This is the simplest setup and requires no additional configuration.

// Standalone mode - react-admin creates the router
import { Admin, Resource } from 'react-admin';
import { tanStackRouterProvider } from 'ra-router-tanstack';

const App = () => (
    <Admin
        dataProvider={dataProvider}
        routerProvider={tanStackRouterProvider}
    >
        <Resource name="posts" list={PostList} />
    </Admin>
);

Embedded Mode

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

import * as React from 'react';
import {
    createRouter,
    createRootRoute,
    createRoute,
    RouterProvider,
    Outlet,
    Link,
} from '@tanstack/react-router';
import { createHashHistory } from '@tanstack/history';
import { Admin, Resource } from 'react-admin';
import { tanStackRouterProvider } from 'ra-router-tanstack';
import { dataProvider } from './dataProvider';
import { PostList, PostEdit } from './posts';

// Define your routes
const rootRoute = createRootRoute({
    component: () => (
        <div>
            <nav>
                <Link to="/">Home</Link>
                <Link to="/admin">Admin</Link>
            </nav>
            <Outlet />
        </div>
    ),
});

const homeRoute = createRoute({
    getParentRoute: () => rootRoute,
    path: '/',
    component: () => <div>Welcome to my app!</div>,
});

// Mount react-admin at /admin
const adminRoute = createRoute({
    getParentRoute: () => rootRoute,
    path: '/admin',
    component: () => (
        <Admin
            dataProvider={dataProvider}
            routerProvider={tanStackRouterProvider}
            basename="/admin"
        >
            <Resource name="posts" list={PostList} edit={PostEdit} />
        </Admin>
    ),
});

const routeTree = rootRoute.addChildren([homeRoute, adminRoute]);

const router = createRouter({
    routeTree,
    history: createHashHistory(),
});

const App = () => <RouterProvider router={router} />;

export default App;

Important: When embedding react-admin, set the basename prop to match the path where react-admin is mounted. In the example above, react-admin is mounted at /admin, so basename="/admin".

Custom Routes

You can use <CustomRoutes> to add custom pages. Use the Route component from tanStackRouterProvider to define routes:

import { Admin, Resource, CustomRoutes } from 'react-admin';
import { tanStackRouterProvider } from 'ra-router-tanstack';

const { Route } = tanStackRouterProvider;

const App = () => (
    <Admin
        dataProvider={dataProvider}
        routerProvider={tanStackRouterProvider}
    >
        <CustomRoutes>
            <Route path="/settings" element={<Settings />} />
            <Route path="/profile" element={<Profile />} />
        </CustomRoutes>
        <CustomRoutes noLayout>
            <Route path="/public" element={<PublicPage />} />
        </CustomRoutes>
        <Resource name="posts" list={PostList} />
    </Admin>
);

Using Router Hooks

When using TanStack Router, import routing hooks from react-admin instead of directly from TanStack Router:

// Recommended - router-agnostic
import { useNavigate, useLocation, useParams } from 'react-admin';

The hooks from react-admin work with both react-router and TanStack Router, making your code portable:

import { useNavigate, useLocation, useParams } from 'react-admin';

const MyComponent = () => {
    const navigate = useNavigate();
    const location = useLocation();
    const { id } = useParams();

    const handleClick = () => {
        navigate('/posts');
        // or navigate(-1) to go back
        // or navigate({ pathname: '/posts', search: '?filter=active' })
    };

    return (
        <div>
            <p>Current path: {location.pathname}</p>
            <p>Record ID: {id}</p>
            <button onClick={handleClick}>Go to Posts</button>
        </div>
    );
};

TanStack Router supports navigation blocking out of the box. The warnWhenUnsavedChanges feature in react-admin forms works automatically:

import { Edit, SimpleForm, TextInput } from 'react-admin';

const PostEdit = () => (
    <Edit>
        <SimpleForm warnWhenUnsavedChanges>
            <TextInput source="title" />
            <TextInput source="body" multiline />
        </SimpleForm>
    </Edit>
);

Unlike react-router (which requires a Data Router for blocking to work), TanStack Router always supports navigation blocking.

Linking Between Pages

Use the LinkBase component from react-admin for router-agnostic links:

import { LinkBase } from 'react-admin';

const Dashboard = () => (
    <div>
        <h1>Dashboard</h1>
        <LinkBase to="/posts">View all posts</LinkBase>
        <LinkBase to="/posts/create">Create a new post</LinkBase>
        <LinkBase to="/posts/123/show">View post #123</LinkBase>
    </div>
);

Or use useCreatePath for dynamic paths:

import { LinkBase, useCreatePath } from 'react-admin';

const Dashboard = () => {
    const createPath = useCreatePath();
    return (
        <div>
            <LinkBase to={createPath({ resource: 'posts', type: 'list' })}>
                Posts
            </LinkBase>
            <LinkBase to={createPath({ resource: 'posts', type: 'create' })}>
                Create Post
            </LinkBase>
            <LinkBase to={createPath({ resource: 'posts', type: 'show', id: 123 })}>
                Post #123
            </LinkBase>
        </div>
    );
};

Limitations

The TanStack Router adapter has some limitations compared to native TanStack Router usage:

Type Safety

TanStack Router’s main feature is compile-time type safety based on route definitions. The react-admin adapter doesn’t provide this level of type safety because react-admin generates routes dynamically from <Resource> components.

Search Params

TanStack Router treats search params as typed objects with validation. The adapter uses string-based search (?key=value) for compatibility with react-admin’s list filters.

Route Loaders

TanStack Router’s data loading features (loader, beforeLoad) are not used by the adapter. React-admin handles data loading through its own dataProvider system.

File-Based Routing

TanStack Router supports file-based routing similar to Next.js. This feature is not compatible with react-admin’s declarative <Resource> approach.