Skip to main content
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.

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
        },
      },
    );
  };
}
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

Build docs developers (and LLMs) love