matchRoute
Finds a URL's route in the route manifest, using the same criteria as SSR and client navigation, and returns its decoded parameters.
import { matchRoute } from "@meekojs/meeko/framework";matchRoute is Meeko's router as a pure function: it takes the route manifest and a URL's pathname and returns the route that handles it with its parameters, or undefined. It uses the same resolution as createFrameworkHandler on the server and mountClientApp when navigating, so you can determine which page would handle a URL outside them: in your own middleware, a service worker, a link migration script or a test.
Usage#
import { createRouteManifest, matchRoute } from "@meekojs/meeko/framework";
const routes = createRouteManifest([
{ id: "/", file: "app/pages/index.meeko", layouts: [] },
{ id: "/users/[id]", file: "app/pages/users/[id].meeko", layouts: [] },
{ id: "/users/new", file: "app/pages/users/new.meeko", layouts: [] },
{ id: "/docs/[...path]", file: "app/pages/docs/[...path].meeko", layouts: [] },
]);
matchRoute(routes, "/users/42"); // { route: { id: "/users/[id]", … }, params: { id: "42" } }
matchRoute(routes, "/users/new"); // { route: { id: "/users/new", … }, params: {} }
matchRoute(routes, "/docs/guide/intro"); // { route: { id: "/docs/[...path]", … }, params: { path: "guide/intro" } }
matchRoute(routes, "/nope"); // undefinedType#
function matchRoute(
routes: readonly RouteRecord[],
pathname: string,
): RouteMatch | undefinedinterface RouteMatch {
readonly route: RouteRecord
readonly params: Readonly<Record<string, string>>
}interface RouteRecord {
readonly id: string
readonly file: string
readonly layouts: readonly string[]
readonly error?: string
readonly sidecar?: string
readonly render?: PageRender
readonly layout?: NamedLayoutRecord
readonly middleware?: readonly string[]
readonly serverMiddleware?: readonly string[]
}Parameters#
| Parameter | Type | Default | Description |
|---|---|---|---|
routes |
RouteRecord[] |
required | The route manifest. createRouteManifest sorts it (static, then dynamic, then catch-all routes) and removes collisions. |
pathname |
string | required | The URL path, starting with /, without a query or fragment (new URL(request.url).pathname). |
Return value#
A RouteMatch { route, params }, or undefined if no route matches.
routeis the manifest'sRouteRecord:id,file,layouts, andrender,sidecar,layout,middleware… if present.paramscontains each decoded dynamic segment (/users/caf%C3%A9gives{ id: "café" }); a catch-all joins its segments with/.
Examples#
What matches and what does not#
import { createRouteManifest, matchRoute } from "@meekojs/meeko/framework";
const routes = createRouteManifest([
{ id: "/users/[id]", file: "app/pages/users/[id].meeko", layouts: [] },
{ id: "/docs/[...path]", file: "app/pages/docs/[...path].meeko", layouts: [] },
]);
matchRoute(routes, "/users/42/")?.params; // { id: "42" }: the trailing slash does not count
matchRoute(routes, "/users/a%2Fb"); // undefined: a segment cannot contain "/"
matchRoute(routes, "/users/.."); // undefined: neither "." nor ".."
matchRoute(routes, "/docs"); // undefined: the catch-all requires at least one segmentYour own host that knows which page each request targets#
// server.mjs, next to the Node build
import { createServer } from "node:http";
import { matchRoute } from "@meekojs/meeko/framework";
import { toNodeListener } from "@meekojs/meeko/node";
import { createNodeApp, manifest } from "./.meeko/output/node/index.mjs";
const app = await createNodeApp();
createServer(toNodeListener(async (request) => {
const match = matchRoute(manifest.routes, new URL(request.url).pathname);
const started = performance.now();
const response = await app.fetch(request);
console.log(match?.route.id ?? "(no route)", response.status, `${Math.round(performance.now() - started)} ms`);
return response;
})).listen(3000);The Node build exports its manifest: metrics or logs can be grouped by route (/users/[id]) rather than by the specific URL.
Common mistakes#
Passing the full URL#
matchRoute(routes, request.url);"http://localhost/users/42" does not start with /: no route matches and the result is undefined.
matchRoute(routes, new URL(request.url).pathname);An array without createRouteManifest, in the wrong order#
const routes = [
{ id: "/users/[id]", file: "a", layouts: [] },
{ id: "/users/new", file: "b", layouts: [] },
];
matchRoute(routes, "/users/new"); // may return /users/[id]matchRoute traverses the list in order. createRouteManifest sorts static routes before dynamic ones and detects collisions (Route collision: a and b both resolve to …).
const routes = createRouteManifest([
{ id: "/users/[id]", file: "a", layouts: [] },
{ id: "/users/new", file: "b", layouts: [] },
]);A route ID without a leading /#
createRouteManifest([{ id: "users", file: "app/pages/users.meeko", layouts: [] }]);TypeError: Route must start with /: users.
createRouteManifest([{ id: "/users", file: "app/pages/users.meeko", layouts: [] }]);Notes#
- Server and browser. A pure function without DOM or I/O: works in any runtime.
- Security. Rejects segments that, once decoded, contain
/,\,.or.., so a parameter never escapes its segment or goes up a directory even when used to read a file. Still, validate parameters with thedefinePageschema. - Case sensitivity. Static segment comparisons are case-sensitive:
/Users/42does not match/users/[id]. - Performance. Traverses the list once per call; for applications with thousands of routes, cache the result if you query it many times per request.