Skip to main content
Refine’s router provider system allows seamless integration with any routing library. It handles route matching, navigation, and URL parameter management automatically.

Supported Routers

Refine officially supports:
  • React Router v6+ - Most popular React routing library
  • Next.js Router - For Next.js applications (Pages and App Router)
  • Remix Router - For Remix applications
  • Expo Router - Community package for React Native

Understanding Router Providers

A router provider connects Refine with your routing library:
const routerProvider = {
  go: () => ({ to, query, hash, options, type }) => void | string,
  back: () => () => void,
  parse: () => () => ParsedParams,
  Link: React.ComponentType<{ to: string; children?: React.ReactNode }>,
};

Quick Setup

1
Choose Your Router
2
Select based on your framework:
3
React Router
npm install @refinedev/react-router react-router
Next.js
npm install @refinedev/nextjs-router
Remix
npm install @refinedev/remix-router
4
Configure the Router Provider
5
React Router
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter, Routes, Route } from "react-router";

const App = () => (
  <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>
);
Next.js (Pages Router)
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router/pages";
import type { AppProps } from "next/app";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Refine
      routerProvider={routerProvider}
      resources={[
        {
          name: "posts",
          list: "/posts",
          create: "/posts/create",
          edit: "/posts/edit/[id]",
          show: "/posts/show/[id]",
        },
      ]}
    >
      <Component {...pageProps} />
    </Refine>
  );
}
Next.js (App Router)
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router/app";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Refine
          routerProvider={routerProvider}
          resources={[
            {
              name: "posts",
              list: "/posts",
              create: "/posts/create",
              edit: "/posts/edit/[id]",
              show: "/posts/show/[id]",
            },
          ]}
        >
          {children}
        </Refine>
      </body>
    </html>
  );
}
Remix
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/remix-router";
import { Outlet } from "@remix-run/react";

export default function App() {
  return (
    <Refine
      routerProvider={routerProvider}
      resources={[
        {
          name: "posts",
          list: "/posts",
          create: "/posts/create",
          edit: "/posts/edit/$id",
          show: "/posts/show/$id",
        },
      ]}
    >
      <Outlet />
    </Refine>
  );
}
6
Define Resources with Routes
7
Map resources to routes:
8
resources={[
  {
    name: "posts",
    list: "/posts",           // List page route
    create: "/posts/create",   // Create page route
    edit: "/posts/edit/:id",   // Edit page route
    show: "/posts/show/:id",   // Show page route
    meta: {
      canDelete: true,          // Enable delete actions
      label: "Blog Posts",      // Display name in menus
      icon: <PostIcon />,       // Icon for navigation
    },
  },
  {
    name: "categories",
    list: "/categories",
  },
]}

useNavigation

Navigate between pages programmatically:
import { useNavigation } from "@refinedev/core";

const MyComponent = () => {
  const { list, create, edit, show, push, replace, goBack } = useNavigation();

  // Navigate to list page
  const handleGoToList = () => {
    list("posts");
  };

  // Navigate to create page
  const handleCreate = () => {
    create("posts");
  };

  // Navigate to edit page
  const handleEdit = (id: string) => {
    edit("posts", id);
  };

  // Navigate to show page
  const handleShow = (id: string) => {
    show("posts", id);
  };

  // Navigate to custom path
  const handleCustom = () => {
    push("/custom-path");
  };

  // Replace current route
  const handleReplace = () => {
    replace("/another-path");
  };

  // Go back
  const handleBack = () => {
    goBack();
  };

  return (
    <div>
      <button onClick={handleGoToList}>Go to Posts</button>
      <button onClick={handleCreate}>Create Post</button>
      <button onClick={() => handleEdit("123")}>Edit Post</button>
      <button onClick={handleBack}>Go Back</button>
    </div>
  );
};

useGo

Lower-level navigation with more control:
import { useGo } from "@refinedev/core";

const MyComponent = () => {
  const go = useGo();

  // Navigate with query params
  const handleNavigate = () => {
    go({
      to: "/posts",
      query: {
        page: 2,
        filters: [
          { field: "status", operator: "eq", value: "published" },
        ],
      },
      type: "push", // or "replace"
    });
  };

  // Navigate with hash
  const handleWithHash = () => {
    go({
      to: "/posts/123",
      hash: "comments",
    });
  };

  // Keep existing query params
  const handleKeepQuery = () => {
    go({
      to: "/categories",
      options: {
        keepQuery: true,
      },
    });
  };

  return <button onClick={handleNavigate}>Navigate</button>;
};

useBack

Navigate to the previous page:
import { useBack } from "@refinedev/core";

const MyComponent = () => {
  const back = useBack();

  return <button onClick={back}>Go Back</button>;
};
Get the Link component from the router provider:
import { useLink } from "@refinedev/core";

const MyComponent = () => {
  const Link = useLink();

  return (
    <Link to="/posts/123">
      View Post
    </Link>
  );
};

Reading Route Parameters

useResource

Get current resource information from the route:
import { useResource } from "@refinedev/core";

const MyComponent = () => {
  const {
    resource,      // Current resource definition
    resources,     // All resources
    action,        // Current action (list, create, edit, show)
    id,            // Record ID from route params
    identifier,    // Resource identifier
  } = useResource();

  return (
    <div>
      <p>Resource: {resource?.name}</p>
      <p>Action: {action}</p>
      <p>ID: {id}</p>
    </div>
  );
};

useParsed

Get parsed route information:
import { useParsed } from "@refinedev/core";

const MyComponent = () => {
  const {
    pathname,       // Current pathname
    params,         // URL and query parameters
    resource,       // Resource from route
    action,         // Action from route
    id,             // ID from route
  } = useParsed();

  return (
    <div>
      <p>Path: {pathname}</p>
      <p>Filters: {JSON.stringify(params?.filters)}</p>
      <p>Current Page: {params?.current}</p>
    </div>
  );
};

useResourceParams

Get resource-specific route parameters:
import { useResourceParams } from "@refinedev/core";

const MyComponent = () => {
  const { resource, action, id } = useResourceParams();

  if (action === "edit" && id) {
    return <EditForm id={id} />;
  }

  if (action === "create") {
    return <CreateForm />;
  }

  return <ListView />;
};

URL State Management

Refine automatically syncs state with URL parameters when syncWithLocation is enabled:
<Refine
  options={{
    syncWithLocation: true,
  }}
  // ...
/>
This automatically syncs:
  • Pagination (page, pageSize)
  • Filters
  • Sorters
  • Search parameters

In Tables

import { useTable } from "@refinedev/core";

const PostList = () => {
  const {
    tableQuery: { data },
    current,
    setCurrent,
    filters,
    setFilters,
  } = useTable({
    syncWithLocation: true, // Sync with URL
  });

  // URL automatically updates when filters or pagination change
  // e.g., /posts?current=2&filters[0][field]=status&filters[0][value]=published
};

In Forms

import { useForm } from "@refinedev/core";

const PostEdit = () => {
  const { onFinish, redirect } = useForm({
    action: "edit",
    redirect: "show", // Redirect to show page after save
  });

  // After successful save, automatically redirects to /posts/show/:id
};

Custom Router Integration

Create a custom router provider:
1
Implement Required Methods
2
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 the URL
      let path = to || "";
      
      // Add query parameters
      if (query) {
        const searchParams = new URLSearchParams();
        Object.entries(query).forEach(([key, value]) => {
          searchParams.append(key, String(value));
        });
        path += `?${searchParams.toString()}`;
      }
      
      // Add hash
      if (hash) {
        path += `#${hash}`;
      }
      
      // Navigate
      if (type === "path") {
        return path;
      }
      
      navigate(path, { replace: type === "replace" });
    };
  },

  back: () => {
    const navigate = useNavigate();
    return () => navigate(-1);
  },

  parse: () => {
    const location = useLocation();
    const params = useParams();
    
    return () => {
      // Parse the current route
      const { pathname, search } = location;
      const queryParams = new URLSearchParams(search);
      
      return {
        pathname,
        params: {
          ...params,
          ...Object.fromEntries(queryParams),
        },
        // Match resource and action from pathname
        resource: undefined, // Implement resource matching
        action: undefined,   // Implement action matching
        id: params.id,
      };
    };
  },

  Link: ({ to, children }) => {
    const Link = useLink(); // Your router's Link component
    return <Link to={to}>{children}</Link>;
  },
};
3
Use the Custom Provider
4
<Refine routerProvider={customRouterProvider} />

Framework-Specific Patterns

React Router - Protected Routes

import { Authenticated } from "@refinedev/core";
import { BrowserRouter, Routes, Route, Navigate } from "react-router";

<BrowserRouter>
  <Refine routerProvider={routerProvider}>
    <Routes>
      {/* Public routes */}
      <Route path="/login" element={<LoginPage />} />
      
      {/* Protected routes */}
      <Route
        element={
          <Authenticated fallback={<Navigate to="/login" />}>
            <Layout>
              <Outlet />
            </Layout>
          </Authenticated>
        }
      >
        <Route path="/posts" element={<PostList />} />
        <Route path="/posts/create" element={<PostCreate />} />
      </Route>
    </Routes>
  </Refine>
</BrowserRouter>

Next.js - Middleware for Auth

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("token");
  
  if (!token && !request.nextUrl.pathname.startsWith("/login")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

Next.js - Dynamic Routes

app/posts/[id]/page.tsx
import { useShow } from "@refinedev/core";

export default function PostShow({ params }: { params: { id: string } }) {
  const { queryResult } = useShow({
    resource: "posts",
    id: params.id,
  });

  const { data } = queryResult;

  return <div>{data?.data.title}</div>;
}

Remix - Loaders for Data Fetching

app/routes/posts.$id.tsx
import { json, LoaderFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export const loader: LoaderFunction = async ({ params }) => {
  const response = await fetch(
    `https://api.example.com/posts/${params.id}`
  );
  const data = await response.json();
  return json({ post: data });
};

export default function PostShow() {
  const { post } = useLoaderData();
  
  return <div>{post.title}</div>;
}
import { useBreadcrumb } from "@refinedev/core";

const Breadcrumb = () => {
  const { breadcrumbs } = useBreadcrumb();

  return (
    <nav>
      {breadcrumbs.map((breadcrumb, index) => (
        <span key={index}>
          {breadcrumb.href ? (
            <a href={breadcrumb.href}>{breadcrumb.label}</a>
          ) : (
            <span>{breadcrumb.label}</span>
          )}
          {index < breadcrumbs.length - 1 && " / "}
        </span>
      ))}
    </nav>
  );
};
import { useMenu } from "@refinedev/core";

const Menu = () => {
  const { menuItems, selectedKey } = useMenu();

  return (
    <nav>
      {menuItems.map((item) => (
        <a
          key={item.key}
          href={item.route}
          className={selectedKey === item.key ? "active" : ""}
        >
          {item.icon}
          {item.label}
        </a>
      ))}
    </nav>
  );
};

Best Practices

  1. Use resource definitions - Define all routes in resource config for consistency
  2. Enable syncWithLocation - Keep URL in sync with table/form state
  3. Use navigation hooks - Don’t construct URLs manually
  4. Handle loading states - Show loading indicators during navigation
  5. Implement 404 pages - Handle unknown routes gracefully
  6. Use protected routes - Secure authenticated pages properly
  7. Preserve query params - Use keepQuery when navigating

Common Patterns

Redirecting After Actions

const { onFinish } = useForm({
  redirect: "show", // or "list", "edit", "create", false
});

// Custom redirect
const { redirect } = useNavigation();
onFinish(values).then((response) => {
  redirect("show", response.data.id);
});

Query Parameter Filters

const { filters, setFilters } = useTable({
  syncWithLocation: true,
  filters: {
    initial: [
      { field: "status", operator: "eq", value: "published" },
    ],
  },
});

// URL becomes: /posts?filters[0][field]=status&filters[0][operator]=eq&filters[0][value]=published
const { modalProps, formProps } = useModalForm({
  action: "create",
  syncWithLocation: true, // Opens modal based on URL
});

// URL with ?modal=true opens the modal automatically

Troubleshooting

Routes Not Matching

Ensure resource routes match your actual route definitions:
// Resource definition
{ name: "posts", list: "/posts", edit: "/posts/edit/:id" }

// Router definition must match
<Route path="/posts/edit/:id" element={<PostEdit />} />

Query Params Not Syncing

Enable syncWithLocation in both Refine and hooks:
<Refine options={{ syncWithLocation: true }} />

useTable({ syncWithLocation: true });

Next Steps

Build docs developers (and LLMs) love