Summary
Matches the given routes to a location and returns the match data. Useful for server-side rendering, testing, or determining which routes would match a given URL without actually navigating.
Signature
function matchRoutes<RouteObjectType extends AgnosticRouteObject>(
routes: RouteObjectType[],
locationArg: Partial<Location> | string,
basename?: string
): AgnosticRouteMatch<string, RouteObjectType>[] | null
Parameters
routes
RouteObjectType[]
required
The array of route objects to match against. Each route can have path, children, and other route properties.
locationArg
Partial<Location> | string
required
The location to match against. Can be:
- A string pathname (e.g.,
/dashboard)
- A partial
Location object with pathname, search, and hash
Optional base path to strip from the location before matching. Useful when your app is served from a subdirectory.
Returns
matches
AgnosticRouteMatch[] | null
An array of matched routes ordered from parent to child, or null if no matches were found.Each match object contains:
params - URL parameters extracted from the path
pathname - The matched portion of the URL
pathnameBase - The matched pathname before child routes
route - The route object that matched
Examples
Basic usage
import { matchRoutes } from "react-router";
const routes = [
{
path: "/",
children: [
{
path: "dashboard",
},
{
path: "settings",
},
],
},
];
const matches = matchRoutes(routes, "/dashboard");
// [
// { params: {}, pathname: "/", pathnameBase: "/", route: {...} },
// { params: {}, pathname: "/dashboard", pathnameBase: "/dashboard", route: {...} }
// ]
With URL parameters
import { matchRoutes } from "react-router";
const routes = [
{
path: "/users/:userId",
children: [
{
path: "posts/:postId",
},
],
},
];
const matches = matchRoutes(routes, "/users/123/posts/456");
// [
// {
// params: { userId: "123" },
// pathname: "/users/123",
// pathnameBase: "/users/123",
// route: {...}
// },
// {
// params: { userId: "123", postId: "456" },
// pathname: "/users/123/posts/456",
// pathnameBase: "/users/123/posts/456",
// route: {...}
// }
// ]
With Location object
import { matchRoutes } from "react-router";
const routes = [
{ path: "/search" },
];
const matches = matchRoutes(routes, {
pathname: "/search",
search: "?q=react",
hash: "#results",
});
With basename
import { matchRoutes } from "react-router";
const routes = [
{ path: "/dashboard" },
];
// App is served from /app subdirectory
const matches = matchRoutes(
routes,
"/app/dashboard",
"/app" // basename
);
// Matches the /dashboard route
Checking for matches
import { matchRoutes } from "react-router";
const routes = [
{ path: "/" },
{ path: "/about" },
];
const matches = matchRoutes(routes, "/contact");
if (matches === null) {
console.log("No route matched");
} else {
console.log(`Matched ${matches.length} route(s)`);
}
Common Use Cases
Server-side rendering
Determine which routes match before rendering:
import { matchRoutes } from "react-router";
import { routes } from "./routes";
export async function handleRequest(request: Request) {
const url = new URL(request.url);
const matches = matchRoutes(routes, url.pathname);
if (!matches) {
return new Response("Not Found", { status: 404 });
}
// Load data for matched routes
const data = await Promise.all(
matches.map((match) => match.route.loader?.())
);
return renderToString({ matches, data });
}
Preloading route data
import { matchRoutes } from "react-router";
function preloadRouteData(pathname: string) {
const matches = matchRoutes(routes, pathname);
if (matches) {
matches.forEach((match) => {
// Preload data for each matched route
if (match.route.loader) {
match.route.loader();
}
});
}
}
// Preload on hover
<Link
to="/dashboard"
onMouseEnter={() => preloadRouteData("/dashboard")}
>
Dashboard
</Link>
Access control checks
import { matchRoutes } from "react-router";
function checkAccess(pathname: string, user: User) {
const matches = matchRoutes(routes, pathname);
if (!matches) return false;
return matches.every((match) => {
const requiredRole = match.route.meta?.requiredRole;
return !requiredRole || user.roles.includes(requiredRole);
});
}
import { matchRoutes } from "react-router";
const routes = [
{ path: "/users/:userId/posts/:postId" },
];
function getParamsFromUrl(url: string) {
const matches = matchRoutes(routes, url);
return matches?.[0]?.params || {};
}
const params = getParamsFromUrl("/users/123/posts/456");
// { userId: "123", postId: "456" }
Testing route configuration
import { matchRoutes } from "react-router";
import { describe, it, expect } from "vitest";
describe("Routes", () => {
it("matches dashboard route", () => {
const matches = matchRoutes(routes, "/dashboard");
expect(matches).not.toBeNull();
expect(matches?.[0].route.path).toBe("/dashboard");
});
it("matches dynamic user route", () => {
const matches = matchRoutes(routes, "/users/123");
expect(matches?.[0].params).toEqual({ userId: "123" });
});
it("returns null for unknown routes", () => {
const matches = matchRoutes(routes, "/unknown");
expect(matches).toBeNull();
});
});
Building breadcrumbs
import { matchRoutes } from "react-router";
function buildBreadcrumbs(pathname: string) {
const matches = matchRoutes(routes, pathname);
if (!matches) return [];
return matches.map((match) => ({
label: match.route.meta?.breadcrumb || match.route.path,
path: match.pathname,
}));
}
const breadcrumbs = buildBreadcrumbs("/users/123/posts/456");
// [
// { label: "Users", path: "/users/123" },
// { label: "Posts", path: "/users/123/posts/456" }
// ]
Route Matching Behavior
Nested routes
Child routes are only matched if their parent matches:
const routes = [
{
path: "/parent",
children: [
{ path: "child" },
],
},
];
matchRoutes(routes, "/parent/child"); // Matches both parent and child
matchRoutes(routes, "/child"); // null - child requires parent
Index routes
const routes = [
{
path: "/parent",
children: [
{ index: true }, // Matches /parent
{ path: "child" }, // Matches /parent/child
],
},
];
matchRoutes(routes, "/parent"); // Matches parent and index
matchRoutes(routes, "/parent/child"); // Matches parent and child
Wildcard routes
const routes = [
{ path: "/" },
{ path: "*" }, // Catch-all
];
matchRoutes(routes, "/anything"); // Matches wildcard route
- Route matching is synchronous and fast
- Results can be cached if routes don’t change
- For large route trees, consider memoizing results
import { matchRoutes } from "react-router";
import { useMemo } from "react";
function useRouteMatches(pathname: string) {
return useMemo(
() => matchRoutes(routes, pathname),
[pathname]
);
}
Type Safety
Use TypeScript generics for type-safe route matching:
import { matchRoutes, RouteObject } from "react-router";
interface CustomRoute extends RouteObject {
meta?: {
title: string;
requiresAuth: boolean;
};
}
const routes: CustomRoute[] = [
{
path: "/dashboard",
meta: { title: "Dashboard", requiresAuth: true },
},
];
const matches = matchRoutes<CustomRoute>(routes, "/dashboard");
if (matches) {
matches.forEach((match) => {
console.log(match.route.meta?.title); // Type-safe access
});
}
Notes
- Returns
null if no routes match (not an empty array)
- Matches are ordered from parent to child (root first, leaf last)
- The
basename is stripped before matching but included in the returned pathnames
- Dynamic segments (
:param) are extracted into the params object
- Optional segments (
path?) and wildcards (*) are supported