Cookbook Router
Practical Patterns

Route metadata

Read route metadata, active metadata chains, and merged metadata directly from the router.

Metadata is application-owned data attached to routes. The core router stores it on normalized routes and exposes helpers for route-specific, branch-specific, and merged access.

Use metadata for breadcrumbs, titles, access policy tags, analytics labels, layout hints, and document metadata.

Attach metadata to routes

import {
  defineRoutes,
  getActiveRouteMetaChain,
  getRouteMeta,
  getRouteMetaChain,
  mergeRouteMetaChain,
} from '@cookbook/router';

const routes = defineRoutes([
  {
    id: 'app',
    path: '/',
    meta: {
      title: 'Dashboard',
      breadcrumb: 'Home',
    },
    children: [
      {
        id: 'reports',
        path: 'reports',
        meta: {
          title: 'Reports',
          breadcrumb: 'Reports',
        },
      },
    ],
  },
] as const);

Metadata does not affect matching by itself. It is data carried with the route model.

Read one route's metadata

const reportsMeta = getRouteMeta(
  router.routes,
  'reports',
);

reportsMeta.title;
// 'Reports'

Unknown route IDs return an empty object.

Read a route metadata chain

const chain = getRouteMetaChain(
  router.routes,
  'reports',
  {
    includeAncestors: true,
  },
);

chain.map((entry) => entry.id);
// ['app', 'reports']

Each entry includes the route ID, params, metadata, normalized route, and optional matched route.

Read the active metadata chain

Use the active match when metadata depends on the current location.

const activeChain = getActiveRouteMetaChain(
  router.state.match,
  {
    includeAncestors: true,
  },
);

This returns an empty array when there is no match.

Merge metadata

const metadata = mergeRouteMetaChain(activeChain, {
  default: 'shallow',
  keys: {
    breadcrumb: 'append',
  },
});
ModeBehavior
leafNearest value wins.
shallowPlain objects merge one level.
deepPlain objects merge recursively.
appendValues are coerced to arrays and appended root-to-leaf.
prependValues are coerced to arrays and prepended leaf-to-root.

Use metadata in a renderer

function getDocumentTitle() {
  const chain = getActiveRouteMetaChain(
    router.state.match,
    {
      includeAncestors: true,
    },
  );

  const meta = mergeRouteMetaChain(chain, {
    default: 'shallow',
  });

  return typeof meta.title === 'string'
    ? meta.title
    : 'Application';
}

The router does not write to document.title. That belongs to the host application.

Where this bites

Metadata is not control flow

Use middleware for redirects, rewrites, and cancellation. Metadata can describe policy, but it does not enforce policy by itself.

Route metadata and active metadata are different reads

getRouteMeta() reads by route ID. getActiveRouteMetaChain() reads from the current match and can include active params.

Merge policy is explicit

There is no universal merge behavior for titles, breadcrumbs, tags, permissions, and layout hints. Pick per-key merge modes where needed.

On this page