Refine has a flexible, framework-agnostic routing system that integrates with popular routing libraries like React Router, Next.js, and Remix through router providers.
Router Provider
The Router Provider is an adapter that connects Refine to your routing library. It provides three core functions:
Interface Definition
From packages/core/src/contexts/router/types.ts:72-79:
export type RouterProvider = {
/**
* Navigate to a specific route
*/
go?: () => GoFunction;
/**
* Navigate back to previous route
*/
back?: () => BackFunction;
/**
* Parse current route information
*/
parse?: () => ParseFunction;
/**
* Link component for navigation
*/
Link?: React.ComponentType<
React.PropsWithChildren<{ to: string }>
>;
};
Integration with React Router
Most Refine applications use React Router:
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/react-router";
function App() {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
edit: "/posts/edit/:id",
show: "/posts/show/:id",
},
]}
>
<Routes>
<Route path="/posts" element={<PostList />} />
<Route path="/posts/create" element={<PostCreate />} />
<Route path="/posts/edit/:id" element={<PostEdit />} />
<Route path="/posts/show/:id" element={<PostShow />} />
</Routes>
</Refine>
</BrowserRouter>
);
}
The @refinedev/react-router package provides a pre-configured router provider for React Router.
Navigation Hooks
useGo
Navigate programmatically:
import { useGo } from "@refinedev/core";
function MyComponent() {
const go = useGo();
const handleClick = () => {
go({
to: "/posts/create",
query: { source: "dashboard" },
options: {
keepQuery: true, // Keep existing query params
keepHash: true, // Keep existing hash
},
type: "push", // or "replace"
});
};
return <button onClick={handleClick}>Create Post</button>;
}
Resource-Based Navigation
Navigate using resource and action instead of paths:
const go = useGo();
// Navigate to list page
go({
to: {
resource: "posts",
action: "list",
},
});
// Navigate to edit page
go({
to: {
resource: "posts",
action: "edit",
id: 123,
},
});
// Navigate to create page
go({
to: {
resource: "posts",
action: "create",
},
query: {
category: "technology",
},
});
Resource-based navigation is more maintainable because route paths are centralized in resource definitions.
useBack
Navigate to the previous page:
import { useBack } from "@refinedev/core";
function EditPage() {
const back = useBack();
return (
<div>
<button onClick={() => back()}>Go Back</button>
{/* Edit form */}
</div>
);
}
useParsed
Get information about the current route:
import { useParsed } from "@refinedev/core";
function MyComponent() {
const parsed = useParsed();
// parsed.resource: Current resource object
// parsed.id: Record ID from URL
// parsed.action: Current action ("list", "create", "edit", "show")
// parsed.params: URL parameters (filters, sorters, etc.)
// parsed.pathname: Current pathname
return (
<div>
<h1>{parsed.resource?.name}</h1>
{parsed.id && <p>Editing ID: {parsed.id}</p>}
<p>Action: {parsed.action}</p>
</div>
);
}
Parsed Params
From packages/core/src/contexts/router/types.ts:45-62:
export type ParsedParams = {
filters?: CrudFilter[];
sorters?: CrudSort[];
currentPage?: number;
pageSize?: number;
[key: string]: any;
};
export type ParseResponse = {
params?: ParsedParams;
resource?: IResourceItem;
id?: BaseKey;
action?: Action; // "create" | "edit" | "list" | "show" | "clone"
pathname?: string;
};
Sync with Location
Enable URL synchronization for table state (filters, sorters, pagination):
<Refine
options={{
syncWithLocation: true,
}}
/>
How It Works
With syncWithLocation: true, the URL reflects table state:
/posts?current=2&pageSize=20&filters[0][field]=status&filters[0][value]=published
Example
import { useTable } from "@refinedev/core";
function PostList() {
const { tableQueryResult, current, pageSize, filters, sorters } = useTable({
resource: "posts",
// syncWithLocation is inherited from <Refine> options
});
// State is automatically synced with URL
// Users can bookmark filtered/sorted views
// Browser back/forward works correctly
return <Table {...tableQueryResult.data} />;
}
With syncWithLocation enabled, users can:
- Share filtered/sorted views via URL
- Bookmark specific table states
- Use browser navigation to move between states
Route Parameters
Dynamic Segments
Use :param syntax for dynamic route segments:
resources={[
{
name: "posts",
edit: "/posts/edit/:id",
show: "/posts/show/:id",
},
]}
Refine automatically extracts id from the URL:
import { useOne } from "@refinedev/core";
function PostEdit() {
// ID is automatically inferred from URL
const { data } = useOne({
resource: "posts",
// id is optional - inferred from route
});
return <Form initialValues={data} />;
}
Custom Parameters
Define custom route parameters:
resources={[
{
name: "posts",
show: "/categories/:categoryId/posts/:id",
},
]}
Access via useParsed:
const { params } = useParsed();
const categoryId = params?.categoryId;
const postId = params?.id;
Nested Routes
Refine works with nested routing structures:
import { Authenticated, Refine } from "@refinedev/core";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
function App() {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
resources={[
{ name: "posts", list: "/posts" },
{ name: "users", list: "/users" },
]}
>
<Routes>
{/* Public routes */}
<Route path="/login" element={<LoginPage />} />
{/* Protected routes */}
<Route
element={
<Authenticated fallback={<Navigate to="/login" />}>
<Layout>
<Outlet />
</Layout>
</Authenticated>
}
>
<Route index element={<Dashboard />} />
<Route path="/posts" element={<PostList />} />
<Route path="/posts/create" element={<PostCreate />} />
<Route path="/posts/edit/:id" element={<PostEdit />} />
<Route path="/users" element={<UserList />} />
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
}
Modal/Drawer Routes
Implement modal or drawer routes:
import { Routes, Route, Outlet } from "react-router";
function App() {
return (
<Routes>
<Route path="/posts" element={<PostList><Outlet /></PostList>}>
{/* Modal routes */}
<Route path="create" element={<PostCreate />} />
<Route path="edit/:id" element={<PostEdit />} />
</Route>
</Routes>
);
}
function PostList({ children }) {
const { data } = useList({ resource: "posts" });
return (
<div>
<Table dataSource={data} />
{/* Render modals as children */}
{children}
</div>
);
}
function PostCreate() {
const back = useBack();
return (
<Modal open onClose={back}>
<Form />
</Modal>
);
}
From examples/app-crm-minimal/src/App.tsx:68-76:
<Route
path="/tasks"
element={
<TasksListPage>
<Outlet />
</TasksListPage>
}
>
<Route path="new" element={<TasksCreatePage />} />
<Route path="edit/:id" element={<TasksEditPage />} />
</Route>
Authentication Routes
Protect routes with authentication:
import { Authenticated } from "@refinedev/core";
import { Routes, Route, Navigate } from "react-router";
function App() {
return (
<Routes>
{/* Protected routes */}
<Route
element={
<Authenticated
fallback={<Navigate to="/login" />}
loading={<LoadingScreen />}
>
<Layout>
<Outlet />
</Layout>
</Authenticated>
}
>
<Route path="/" element={<Dashboard />} />
<Route path="/posts" element={<PostList />} />
</Route>
{/* Auth routes */}
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Routes>
);
}
The <Authenticated> component uses the check method from your auth provider to verify authentication.
Redirects After Actions
Configure default redirects after CRUD operations:
<Refine
options={{
redirect: {
afterCreate: "list", // or "edit" | "show" | false
afterEdit: "list", // or "show" | false
afterClone: "list", // or "edit" | false
},
}}
/>
Per-Hook Redirects
Override defaults in individual hooks:
import { useCreate } from "@refinedev/core";
function PostCreate() {
const { mutate } = useCreate();
const handleSubmit = (values) => {
mutate(
{
resource: "posts",
values,
redirect: "show", // Override default
},
{
onSuccess: (data) => {
// Navigate to show page with new record ID
},
},
);
};
}
Breadcrumbs
Refine automatically generates breadcrumbs based on resources and routes:
import { useBreadcrumb } from "@refinedev/core";
function Breadcrumbs() {
const { breadcrumbs } = useBreadcrumb();
return (
<nav>
{breadcrumbs.map((breadcrumb) => (
<a key={breadcrumb.label} href={breadcrumb.href}>
{breadcrumb.label}
</a>
))}
</nav>
);
}
Example breadcrumb trail:
Home > Posts > Edit > "My Blog Post"
Custom Router Provider
Create a custom router provider for unsupported routing libraries:
import { RouterProvider } from "@refinedev/core";
import { useNavigate, useLocation, useParams } from "your-router";
const customRouterProvider: RouterProvider = {
go: () => {
const navigate = useNavigate();
return ({ to, query, hash, options, type }) => {
// Build URL with query and hash
const url = buildUrl(to, query, hash);
// Navigate
if (type === "replace") {
navigate(url, { replace: true });
} else {
navigate(url);
}
};
},
back: () => {
const navigate = useNavigate();
return () => navigate(-1);
},
parse: () => {
const location = useLocation();
const params = useParams();
return () => {
// Parse location and return resource, action, id, params
return {
resource: determineResource(location.pathname),
action: determineAction(location.pathname),
id: params.id,
params: parseQueryString(location.search),
pathname: location.pathname,
};
};
},
Link: YourRouterLink,
};
Best Practices
1. Use Resource-Based Navigation
Prefer resource-based navigation over hardcoded paths:
// Good
go({ to: { resource: "posts", action: "edit", id: 123 } });
// Less flexible
go({ to: "/posts/edit/123" });
2. Enable syncWithLocation for Tables
Always enable for better UX:
<Refine options={{ syncWithLocation: true }} />
3. Handle Loading States
<Authenticated
fallback={<Navigate to="/login" />}
loading={<LoadingSpinner />} // Avoid flash of content
>
<YourApp />
</Authenticated>
4. Use Nested Routes for Modals
Keep modal state in URL:
// URL: /posts/edit/123
// Shows PostList with edit modal open
<Route path="/posts" element={<PostList><Outlet /></PostList>}>
<Route path="edit/:id" element={<EditModal />} />
</Route>
Next Steps
Resources
Learn about resource configuration
Authentication
Implement authentication flows
Data Fetching
Fetch and manage data
React Router Integration
Deep dive into React Router setup