Skip to main content
Refine AI
Version: 4.xx.xx

React Router

Refine provides router bindings and utilities for React Router. It is built on top of the react-router package. This package will provide easy integration between Refine and react-router for both existing projects and new projects.

npm i @refinedev/react-router react-router

We recommend using create refine-app to initialize your Refine projects. It gives you a good boilerplate to start with using React Router.

npm create refine-app@latest  -- -p refine-react my-refine-app

Refer to the Router Provider documentation for detailed information.

Usage

@refinedev/react-router is not restricting you to use the router in a specific way and it is up to you to decide how you want to use it.

You can define your routes the way you want, then pass the routerProvider prop to the Refine component and use the resources prop to define the resources and their action paths. From basic to advanced use cases and enterprise applications, you will have full control over your routes. In our examples, we've used this approach to demonstrate the flexibility of the router provider and the route handling process.

Basic Usage

We'll pass the routerProvider prop to the Refine component to instruct Refine on how to communicate with the router. We'll also define our resources and their action paths, this will inform Refine to use these paths when generating the breadcrumbs, menus, handling redirections and inferring the current resource and action.

This example uses the <BrowserRouter> router component.

App.tsx
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter, Routes, Route } from "react-router";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};

Usage with Authentication

When handling authenticated routes, we can use <Authenticated> to check if the user is authenticated or not. Internally, it uses the useIsAuthenticated hook and handles the redirection or showing the appropriate elements based on the authentication status by the children and fallback props.

Additionally, we'll use the <Outlet> component from react-router to render our routes inside the <Authenticated> component. This will allow us to create protected routes and render the routes only when the user is authenticated.

We will also need to create a /login route to handle the redirection when the user is not authenticated. We can use the AuthPage components from Refine's UI packages with type="login" prop to render the login page.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { AuthPage } from "@refinedev/antd";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>
<Route
element={
<Authenticated redirectOnFail="/login">
<Outlet />
</Authenticated>
}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};

Notice that we've used the fallback property to render the <Outlet> component inside the wrapper Route of the /login page. This allows us to render the login page when the user is not authenticated and redirect the user to the /posts page when the user is authenticated.

We've also used the <Outlet> component inside the children of the <Authenticated> component in the wrapper Route of the resource routes. This will allow us to render the resource routes only when the user is authenticated and redirect the user to the /login page when the user is not authenticated.

Usage with Layouts

When using layouts in your application, you can use the same approach as the authentication example. We'll use the <ThemedLayoutV2> component to wrap our routes and the <Outlet> component from react-router to render our routes inside the <ThemedLayoutV2> component. This will allow us to define the common layout for our routes.

In the below example, we'll wrap our resource routes with the Layout component from @refinedev/antd and render the routes inside the <Outlet> component.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { AuthPage, ThemedLayoutV2 } from "@refinedev/antd";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>
<Route
element={
<Authenticated redirectOnFail="/login">
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};

Notice that we've wrapped the <Outlet> with <ThemedLayoutV2> component. This way, we don't need to define the layout for each route and wrap the each route inside it with the <ThemedLayoutV2> component.

Usage with Access Control providers

If you want to protect your routes with Access Control Provider, all you have to do is to wrap Outlet with CanAccess component.

CanAccess component will infer resource name and action based on the current route and handle the access control from your Access Control Provider for you.

App.tsx
import { Refine, Authenticated, CanAccess } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { AuthPage, ThemedLayoutV2 } from "@refinedev/antd";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>
<Route
element={
<Authenticated redirectOnFail="/login">
<ThemedLayoutV2>
<CanAccess fallback={<div>Unauthorized!</div>}>
<Outlet />
</CanAccess>
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};

If you don't want to wrap your whole application with CanAccess, it's also possible to wrap certain routes individually.

App.tsx
<Routes>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>
<Route
element={
<Authenticated redirectOnFail="/login">
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route
path="create"
element={
<CanAccess fallback={<div>Unauthorized!</div>}>
<PostCreate />
</CanAccess>
}
/>
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
</Route>
</Routes>

Usage with an Error Page

You may also want to render an error page when the user tries to access a route that doesn't exist. To do this, we'll define a * route that will render the error page when there's no other route that matches the current path.

We'll place this inside the authenticated routes so that the unauthorized users will be redirected to the login page when they try to access a route that doesn't exist.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { AuthPage, ThemedLayoutV2, ErrorComponent } from "@refinedev/antd";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>
<Route
element={
<Authenticated redirectOnFail="/login">
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};

Usage with a Root Route

You may notice that we didn't define an index route for our application yet. We can defina a root route that will redirect the user to the posts resource when they visit the root of our application.

We can achieve this by using the <NavigateToResource> component. This component will redirect the user to the list page of the given resource.

We also want this route to be rendered only when the user is authenticated. We can achieve this by placing our Route inside the authenticated routes.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, { NavigateToResource } from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { AuthPage, ThemedLayoutV2, ErrorComponent } from "@refinedev/antd";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>
<Route
element={
<Authenticated redirectOnFail="/login">
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route index element={<NavigateToResource resource="posts" />} />
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
<Route path="categories">
<Route index element={<CategoryList />} />
<Route path="show/:id" element={<CategoryShow />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};

Your action definitions in the resources can contain additional parameters and nested routes. Passing these parameters when navigating to the pages are handled by the current available parameters and the meta props of the related hooks and components.

Refine supports route parameters defined with :param syntax. You can use these parameters in your action definitions and create your routes accordingly. For example, if you have a posts resource and you want to create a route for the show action of a specific post, you can define the show action as /posts/show/:id and use the id parameter in your component.

Additional Components

@refinedev/react-router-v6 package also includes some additional components that can be useful in some cases.

A basic component that extends the Navigate component from react-router to navigate to a resource page. It is useful when you want to navigate to a resource page at the index route of your app.


const App = () => {
return (
<BrowserRouter>
<Refine
/* ... */
resources={[
{
name: "posts"
list: "/posts",
},
]}
>
<Routes>
<Route path="/" element={<NavigateToResource resource="posts" />} />
<Route path="/posts" element={<PostList />} />
</Routes>
</Refine>
</BrowserRouter>
)
}

Properties

resource (optional) - The name of the resource to navigate to. It will redirect to the first list route in the resources array if not provided.

meta (optional) - The meta object to use if the route has parameters in it. The parameters in the current location will also be used to compose the route but meta will take precedence.

UnsavedChangesNotifier

This component enables the warnWhenUnsavedChanges feature of Refine. It will show a warning message when user tries to navigate away from the current page without saving the changes. Also checks for beforeunload event to warn the user when they try to close the browser tab or window.

Place this component inside the <Refine> components children to enable this feature.

const App = () => {
return (
<BrowserRouter>
<Refine
/* ... */
options={{
/* ... */
warnWhenUnsavedChanges: true,
}}
>
{/* ... */}
<UnsavedChangesNotifier />
</Refine>
</BrowserRouter>
);
};

Properties

translationKey (optional) - The translation key for the warning message. Default value is warnWhenUnsavedChanges. Useful when you use an i18n provider.

message (optional) - The warning message. Default value is Are you sure you want to leave? You have unsaved changes. Useful when you don't use an i18n provider.

CatchAllNavigate

It will redirect to the given path and keep the current location in to query parameter to redirect back when needed. In some cases you may not want to use the <Authenticated> component's redirectOnFail prop to redirect and have a catch-all route to redirect to the desired page. This is useful when handling the 404 pages with authentication.

import { Refine } from "@refinedev/core";
import routerProvider, { CatchAllNavigate } from "@refinedev/react-router";
import { AuthPage } from "@refinedev/antd";

import { BrowserRouter, Routes, Route } from "react-router";

import authProvider from "src/authProvider";

const App = () => {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
},
]}
>
<Routes>
<Route path="/login" element={<AuthPage type="login" />} />
{/* ... */}
<Route path="*" element={<CatchAllNavigate to="/login" />} />
</Routes>
</Refine>
</BrowserRouter>
);
};

Properties

to (required) - The path to redirect to.

DocumentTitleHandler

This component will generate the document title for the current page.By default, it follows a set of predefined rules to generate titles based on the provided props. However, it also offers the flexibility to customize the title generation process by providing a custom handler function. The default title generation rules are as follows:

  • list : Posts | Refine
  • edit : #{id} Edit Post | Refine
  • show : #{id} Show Post | Refine
  • create : Create new Post | Refine
  • clone : #{id} Clone Post | Refine
  • default : Refine
const App = () => {
return (
<BrowserRouter>
<Refine
/* ... */
>
{/* ... */}
<DocumentTitleHandler />
</Refine>
</BrowserRouter>
);
};

Properties

handler (optional) - The function that will get invoked in every location change. It will receive an object with the following properties:

  • pathname: The current URL pathname.
  • resource: The resource being displayed (e.g., "posts").
  • action: The action being performed (e.g., "edit", "show", "create").
  • autoGeneratedTitle: The default auto-generated title based on the predefined rules.
  • params: An object containing URL parameters, including the id parameter if present.

Inside the handler function, you can dynamically generate the document title based on the provided properties and any additional logic required. The function should return the generated title.

const customTitleHandler = ({ resource, action, params }) => {
let title = "Custom default"; // Default title

if (resource && action) {
title = `${resource} ${action} ${params.id}`;
}

return title;
};

// Usage
<DocumentTitleHandler handler={customTitleHandler} />;

Hooks

useDocumentTitle

This hook allows you to set the document title for the current page. It can be used in any component that is a child of the <Refine> component.

import { useDocumentTitle } from "@refinedev/react-router";

const PostList = () => {
useDocumentTitle("Posts | Refine");

return <List>{/* ... */}</List>;
};

This hook can take an object as an argument with i18nKey. This key will be used to translate the title using the i18n provider.

import { useDocumentTitle } from "@refinedev/react-router";

const PostList = () => {
useDocumentTitle({ i18nKey: "documentTitle.posts.list" });

return <List>{/* ... */}</List>;
};

This hook also returns a function that can be used to set the document title dynamically.

import { useDocumentTitle } from "@refinedev/react-router";

const PostList = () => {
const setTitle = useDocumentTitle();

useEffect(() => {
setTitle("Posts | Refine");
}, []);

return <List>{/* ... */}</List>;
};

FAQ

How to handle optional authentication, redirects and layouts with authentication?

In the below example, you'll find different cases for route definitions, we've used Authenticated component to handle authentication and redirects. You can always choose to use a different approach, Refine will allow you to handle the routes however you like.

For optional authentication, in our authProvider implementation's check method, we can pass authentication: false and redirectTo: undefined to indicate that the current user is not authenticated but we don't want to redirect them to the login page. This is useful, when some pages in our app are public and don't require authentication and some pages are private and require authentication.

authProvider.ts
import { AuthProvider } from "@refinedev/core";

export const authProvider: AuthProvider = {
check: async () => {
const isAuthenticated = await yourMethodToCheckIfUserIsAuthenticated();

return {
authentication: isAuthenticated,
// notice that we omit the `redirectTo` property
};
},
// ...
};

In our App.tsx, while defining the routes, we'll leverage the Outlet component from react-router and Authenticated component from @refinedev/core.

Initialization of <Refine> component

Let's start with initializing our <Refine> component with inside <BrowserRouter> component. We'll pass our dataProvider routerProvider and authProvider to the <Refine> component. We'll also pass our resources and define our action paths for each resource in <Refine> component.

App.tsx
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";

import { BrowserRouter, Routes } from "react-router";

import { authProvider } from "src/authProvider";

export const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
]}
>
{/* ... */}
</Refine>
</BrowserRouter>
);
};

Defining routes

Then, let's start adding our routes. We'll start with the LandingPage component at the / path. This will be visible for both authenticated and unauthenticated users. We need to wrap our Route elements with a Routes component.

App.tsx
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";

+ import { BrowserRouter, Routes, Route } from "react-router";

import { authProvider } from "src/authProvider";

+ import { LandingPage } from "pages/landing";

export const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
]}
>
+ <Routes>
+ <Route index element={<LandingPage />} />
+ </Routes>
</Refine>
</BrowserRouter>
)
}

Defining authenticated routes

Now, let's create our resource actions. They will be wrapped with the Layout component and only visible for authenticated users. We'll use the Authenticated component to handle authentication and redirects. We'll also use the Outlet component to properly wrap and handle the authenticated routes.

App.tsx
+ import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";

+ import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { LandingPage } from "pages/landing";
+ import { PostList, PostCreate } from "pages/posts";

+ import { Layout } from "components/layout";

export const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
]}
>
<Routes>
<Route index element={<LandingPage />} />
+ <Route
+ element={(
+ <Authenticated redirectOnFail="/login">
+ <Layout>
+ <Outlet />
+ </Layout>
+ </Authenticated>
+ )}
+ >
+ <Route path="posts">
+ <Route index element={<PostList />} />
+ <Route path="create" element={<PostCreate />} />
+ </Route>
+ </Route>
</Routes>
</Refine>
</BrowserRouter>
)
}

Now, when we navigate to the /posts page we should either see the PostList component or be redirected to the /login page. If we're already authenticated, we should see the PostList component.

Defining auth pages

We can now add our /login and /register pages. We'll use the AuthPage component for both pages. We'll also navigate to the /posts page if the user is already authenticated.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
+ import routerProvider, { NavigateToResource } from "@refinedev/react-router";

import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

import { LandingPage } from "pages/landing";
import { PostList, PostCreate } from "pages/posts";
+ import { AuthPage } from "pages/auth";

import { Layout } from "components/layout";

export const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
]}
>
<Routes>
<Route index element={<LandingPage />} />
<Route
element={(
<Authenticated redirectOnFail="/login">
<Layout>
<Outlet />
</Layout>
</Authenticated>
)}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
</Route>
+ <Route
+ element={(
+ <Authenticated fallback={<Outlet />}>
+ <NavigateToResource resource="posts" />
+ </Authenticated>
+ )}
+ >
+ <Route path="/login" element={<AuthPage type="login" />} />
+ <Route path="/register" element={<AuthPage type="register" />} />
+ </Route>
</Routes>
</Refine>
</BrowserRouter>
)
}

Now, when we navigate to the /login or /register pages, we should either see the AuthPage component or be redirected to the /posts page. If we're already authenticated, we should be redirected to the /posts page.

Defining error page

Finally, we'll add our ErrorComponent component to show when user navigates to a non-existing page.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, { NavigateToResource } from "@refinedev/react-router";

import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "src/authProvider";

+ import { ErrorComponent } from "components/error";
import { LandingPage } from "pages/landing";
import { PostList, PostCreate } from "pages/posts";
import { AuthPage } from "pages/auth";

import { Layout } from "components/layout";

export const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
},
]}
>
<Routes>
<Route index element={<LandingPage />} />
<Route
element={(
<Authenticated redirectOnFail="/login">
<Layout>
<Outlet />
</Layout>
</Authenticated>
)}
>
<Route path="posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
</Route>
</Route>
<Route
element={(
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
)}
>
<Route path="/login" element={<AuthPage type="login" />} />
<Route path="/register" element={<AuthPage type="register" />} />
</Route>
+ <Route path="*" element={<ErrorComponent />} />
</Routes>
</Refine>
</BrowserRouter>
);
};

Result

We've now added our AuthPage and ErrorComponent components to our app. We've also used the Authenticated component to our routes to redirect the users to the /login page if they're not authenticated. The index page is available for all users because we didn't wrap it with the Authenticated component.

Handling 404s

In the earlier versions of Refine, if authProvider was defined, we've redirected the users to the /login route even with the 404s and 404 pages were only available to the authenticated users. Now, the routes are handled by the users, so you can handle the 404s however you like.

404 Pages for both authenticated and not authenticated users

Here's an example for rendering the ErrorComponent for undefined routes for both authenticated and not authenticated users.

Let's start with defining the Refine component.

App.tsx
import { Refine, Authenticated } from "@refinedev/core";
import routerProvider, { CatchAllNavigate } from "@refinedev/react-router";
import dataProvider from "@refinedev/simple-rest";

import { BrowserRouter, Routes, Route, Outlet } from "react-router";

import { authProvider } from "providers/authProvider";

import { ErrorPage } from "pages/error";
import { AuthPage } from "pages/auth";
import { PostList, CategoryList } from "pages/posts";
import { Layout } from "components/Layout";

export const App = () => {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
authProvider={authProvider}
resources={[
{
name: "posts",
list: "/posts",
},
{
name: "categories",
list: "/categories",
},
]}
>
<Routes>{/* ... */}</Routes>
</Refine>
</BrowserRouter>
);
};

Now, we can add the routes with authentication control. We should place them inside the Routes component.

<Route
element={
<Authenticated fallback={<CatchAllNavigate to="/login" />}>
<Layout>
<Outlet />
</Layout>
</Authenticated>
}
>
<Route path="/posts" element={<PostList />} />
<Route path="/categories" element={<CategoryList />} />
</Route>

This will render the /posts and /categories routes for authenticated users and apply the Layout when rendering. If the current visitor is not authenticated, it will redirect them to the /login route.

Let's add the /login route.

<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage type="login" />} />
</Route>

This will render the /login route for not authenticated users and redirect the authenticated users to the /posts route.

And finally, we will add a catch-all route (*) and render the ErrorPage component.

<Route
element={
<Authenticated fallback={<Outlet />}>
<Layout>
<Outlet />
</Layout>
</Authenticated>
}
>
<Route path="*" element={<ErrorPage />} />
</Route>

We will render the ErrorPage component for both authenticated and not authenticated users. Only authenticated users will be able to use the sider component we have in the layout.

404 Pages for authenticated users only

The difference from the previous example is in the wrapper of the * route. Now we will redirect the unauthenticated users to the /login route and show the ErrorPage component for authenticated users only.

<Route
element={
<Authenticated fallback={<CatchAllNavigate to="/login" />}>
<Layout>
<Outlet />
</Layout>
</Authenticated>
}
>
<Route path="*" element={<ErrorPage />} />
</Route>

We can also omit the fallback property and let the default redirect flow handle the unauthenticated users.

<Route
element={
<Authenticated>
<Layout>
<Outlet />
</Layout>
</Authenticated>
}
>
<Route path="*" element={<ErrorPage />} />
</Route>

This means we will look for the redirectTo property in the authProvider's check method. If it's defined, <Authenticated> component will redirect the user to the redirectTo route.

RefineRoutes Component

This component is available but not recommended to use. While this works for the simple cases, we encourage you to define your routes using the Route components to have more control and flexibility over them.

This component can be used to create routes for your resources by using the resources prop. It will only take effect if the action properties in the resource definitions are assigned to components or objects with component property.

It will create the routes and pass it as a JSX.Element[] to the children function. You can use this to wrap your routes with other components like Authenticated or Layout.

App.tsx
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, { RefineRoutes } from "@refinedev/react-router";
import { BrowserRouter, Routes, Route } from "react-router";

import { PostList, PostCreate } from "pages/posts";
import { CategoryList, CategoryShow } from "pages/categories";

import { Layout } from "components/Layout";
import { ErrorComponent } from "components/Error";

const App = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
routerProvider={routerProvider}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
},
{
name: "categories",
list: CategoryList,
show: {
component: CategoryShow,
path: "/categories/:id/details", // Notice that we can also define the path here
},
}
]}
>
<RefineRoutes>
{(routes) => (
<Routes>
<Route
element={(
<Layout>
<Outlet />
</Layout>
)}
>
<Route index element={<NavigateToResource />} />
{routes}
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
)}
</RefineRoutes>
</BrowserRouter>
)
}

We've defined our resource actions using components to let the RefineRoutes render them. We can also define the path for each action. If we don't define the path, the RefineRoutes will use the default paths for the actions.

Refer to "Understanding the Resources" section of our tutorial for detailed information.

💡 We also defined the show action's path as /categories/:id/details which will override the default path.

The index route is defined with the NavigateToResource component which will redirect the user to the list page of the first defined resource.

We also added a catch-all route which will render the ErrorComponent for the routes that are not defined.

When components are used to define the resource actions, default paths will be used. You can override the default paths by assigning an object with component and path properties to the action properties.

Default paths are:

  • list: /resources
  • create: /resources/create
  • edit: /resources/edit/:id
  • show: /resources/show/:id

How to change the document title?

By default <DocumentTitleHandler/> component will generate the document title based on current resource and action with the "Refine" suffix. You can customize the title generation process by providing a custom handler function.

import { BrowserRouter, DocumentTitleHandler } from "@refinedev/react-router";
import { Refine } from "@refinedev/core";

const App = () => {
return (
<BrowserRouter>
<Refine
/* ... */
>
{/* ... */}
<DocumentTitleHandler
handler={({ action, params, resource }) => {
const id = params?.id ?? "";

const actionPrefixMatcher = {
create: "Create new ",
clone: `#${id} Clone ${resource?.meta?.label}`,
edit: `#${id} Edit ${resource?.meta?.label}`,
show: `#${id} Show ${resource?.meta?.label}`,
list: `${resource?.meta?.label}`,
};

const suffix = " | <Company Name>";
const title = actionPrefixMatcher[action || "list"] + suffix;

return title;
}}
/>
</Refine>
</BrowserRouter>
);
};