Skip to content

Routing glossary

Optional routing capability: the URL is an input, the active route is ordinary state via subscriptions, and navigation is an event. See The model.

Change the route by dispatching navigation. The active route is a subscription you read like any other. Because navigation is an event, it is traceable, interceptable, and rewound by time-travel.

(rf/dispatch [:rf.route/navigate {:to :app/article :params {:id "abc"}}])

Related: The model.

route

A URL pattern registered with reg-route under an id, paired with match behaviour — :params/:query schemas, a loader, a :can-leave / :can-enter guard, scroll policy. The route table is the app's URL map.

route params

The active URL as state: read :rf.route/id, :rf.route/params, and :rf.route/query through subscriptions. Path params and ?query= values (coerced and defaulted) drive handlers and views; ?page=2 survives Back for free.

loader

What a route declares it needs on entry — :resources ensured loaded — so a page's data requirement sits next to its URL. Loaders also run on the server; no separate SSR data-fetch to keep in sync. A route's :on-match events are its activation work, not its loader: the runtime fires and forgets them, and they never touch route readiness. See Activation work and page data.

effective route plan

The resource requirements a navigation actually runs: every :resources entry contributed by the route's chain, parent-most to leaf, with identical requirements deduped to one fetch. Naming a :parent is what opts a child in, so a shared shell read is declared once instead of restated per tab. Only :resources compose this way.

intent prefetch

Warming a destination's effective route plan before the user commits, via [route-link {… :prefetch :intent}] or a direct [:rf.route/prefetch <address>] dispatch. Hover, focus, or touch runs the same plan a navigation would — ownerless, non-blocking, and with no route state, guards, or :on-match. Click through and the ordinary resource dedupe reuses the warmed work.

route chain

The active route's ancestry. A route names a :parent; @(subscribe [:rf.route/chain]) returns the lineage root-most first — on /articles/intro, [:app/articles :app/article]. Shared layouts without <Outlet/>: the leaf is the page; each ancestor wraps a shell. See Nested layouts.

transition

Route readiness — :idle, :loading, or :error — via :rf.route/transition, with the structured failure on :rf.route/error. It is a projection over the blocking :resources in the effective route plan: pending on a first load, :error on a blocking first-load failure or a plan that could not be built, :idle otherwise. A background refresh, a non-blocking read, an intent prefetch, and :on-match never move it. One global fact for a progress bar or error banner, not per-page loading flags.

Counter that identifies one navigation. Route-declared resources are owned by the token that planned them; a reply after a newer navigation is dropped instead of overwriting the page you are on. Hand-rolled loaders opt in via the :rf.route/nav-token coeffect and :rf.route/with-nav-token fx.

route guard

A boolean subscription on a route: :can-leave (true = leave is fine) or :can-enter (true = enter is fine). The two refusals are deliberately asymmetric. A :can-leave false parks the attempt in [:rf/pending-navigation] — a question to the user — and your view resolves it with [:rf.route/continue <id>] or [:rf.route/cancel <id>] (the pending-nav id). A :can-enter false is terminal: a question to application state, answered the same way every time, so nothing commits and nothing parks. Unsaved changes → leave guard (recipe); per-route auth → enter guard (recipe); multi-route policy → optional interceptor.

terminal entry

What a refused :can-enter does: commit no route slice, URL, scroll, resource, or :on-match; park no pending value; and dispatch :rf.route/entry-denied exactly once with the replayable :destination. There is nothing to resume and no entry bypass — the return after signing in is a fresh navigation whose guard re-evaluates naturally. Under SSR the same refusal renders the shell under a 403.

not-found

Reserved route id :rf.route/not-found. The runtime activates it when no pattern matches — or when URL params fail their schema — with the offending URL in params. Ordinary route you register and design; skip it and unmatched URLs get a bare placeholder.

url-bound?

Flag that this frame owns the browser address bar. At most one frame is url-bound (none is legal — URL pushes then no-op). Its navigations write the URL; Back/Forward (popstate) dispatch to it. Other frames route in memory only — how a sidecar like Xray coexists without fighting over the URL.