useRoute
Reads the committed route rendered by the component (url, path, id, params and search) as signals, and checks whether a route is active.
Se autoimporta en las aplicaciones del framework.
import { useRoute } from "@meekojs/meeko/framework";useRoute() gives the component the route it is rendering: the request's route in SSR and the last committed navigation's route in the browser. Each value is a read-only signal, so a template, computed or effect reading it updates on navigation, including in a layout retained between pages. isActive(to, options) applies the same rule as <MeekoLink match> to custom links and components.
Works in pages, layouts, app/app.meeko, app/error.meeko and any components they render. It is auto-imported in applications (your own export named useRoute in an auto-import folder takes priority) and can also be called from code running with the component (a composable, computed, effect). In an event handler there is no active root and it throws: request it in the script and use the result in the handler.
Usage#
<!-- app/layouts/default.meeko -->
<script lang="ts">
const route = useRoute();
</script>
<template>
<nav>
<a href="/" bind:aria-current="route.isActive('/')">Home</a>
<a href="/about" bind:aria-current="route.isActive('/about')">About</a>
</nav>
<p class="path">You are at {route.path.value}</p>
<main><MeekoOutlet /></main>
</template>isActive returns "page" on the exact route and false elsewhere; bind: removes the attribute for false.
Type#
function useRoute(): RouteStateinterface RouteState {
readonly url: ReadonlySignal<string>;
readonly path: ReadonlySignal<string>;
readonly id: ReadonlySignal<string | null>;
readonly params: ReadonlySignal<Readonly<Record<string, unknown>>>;
readonly search: ReadonlySignal<Readonly<Record<string, unknown>>>;
isActive<K extends RouteId>(
to: K,
...options: ActiveArguments<K>
): "page" | "true" | false;
}Parameters#
useRoute() takes no parameters. The result's isActive(to, options) method takes:
| Option | Type | Default | Description |
|---|---|---|---|
to |
RouteId |
required | An existing route ID, such as "/users/[id]". A nonexistent route is a type error. |
params |
to's RouteParams |
none | Dynamic segments, as in href(). Required for a dynamic route, forbidden otherwise. |
prefix |
boolean |
false |
Also matches routes below to, with "true". "/" only matches exactly. |
Return value#
A RouteState, the same object throughout the application tree:
| Property | Type | Description |
|---|---|---|
url |
ReadonlySignal<string> |
Origin-relative URL: pathname, search and, in the browser, fragment (/users/42?tab=a#bio). In SSR it never contains a fragment because the browser does not send it. |
path |
ReadonlySignal<string> |
pathname, encoded as in location.pathname (/users/42). |
id |
ReadonlySignal<string | null> |
Route ID in the manifest ("/users/[id]"), or null in the app/error.meeko 404 page for a URL without a route. |
params |
ReadonlySignal<Readonly<Record<string, unknown>>> |
Route parameters as received by the page (validated by its schema). |
search |
ReadonlySignal<Readonly<Record<string, unknown>>> |
Search parameters as received by the page; in the 404, the query string with repeated keys as arrays. |
isActive(to, options?) |
"page" | "true" | false |
Since 1.5. A function, not a signal. "page" if the committed route is to (with its params); with prefix: true, "true" if below it by whole segments; otherwise false. |
useRoute() takes no arguments. Outside a Meeko application (a framework render or mountClientApp), it throws Error: useRoute() necesita una aplicación de Meeko en ejecución….
Examples#
A sidebar with the active section#
<!-- app/layouts/default.meeko -->
<script lang="ts">
const route = useRoute();
const links = [
{ to: "/", label: "Home" },
{ to: "/users", label: "Users" },
{ to: "/settings", label: "Settings" },
] as const;
const inSettings = computed(() => route.isActive("/settings", { prefix: true }) !== false);
</script>
<template>
<aside>
<a each:of="links" each:as="link" each:key="link.to" bind:href="href(link.to)" bind:aria-current="route.isActive(link.to, { prefix: true })">{link.label}</a>
<p if:when="inSettings.value">Changes are saved when you leave.</p>
</aside>
<main><MeekoOutlet /></main>
</template>On /settings/profile, “Settings” has aria-current="true"; on /settings, aria-current="page".
A dynamic route#
<script lang="ts">
const route = useRoute();
const props = defineProps<{ userId: string }>();
</script>
<template>
<a bind:href="href('/users/[id]', { params: { id: props.userId } })"
bind:aria-current="route.isActive('/users/[id]', { params: { id: props.userId } })">Profile</a>
</template>React to each navigation#
import { effect } from "@meekojs/meeko";
import { useRoute } from "@meekojs/meeko/framework";
// In the <script> of app/app.meeko: one page view per confirmed navigation.
const route = useRoute();
effect(() => {
const url = route.url.value;
if (typeof window !== "undefined") navigator.sendBeacon?.("/api/views", url);
});The effect runs during hydration and each committed navigation, including going back or changing only the fragment.
Read the typed query string#
search has the shape received by the page. Without a schema, each value is unknown: check it before use.
<script lang="ts">
const route = useRoute();
const tab = computed(() => {
const value = route.search.value.tab;
return typeof value === "string" ? value : "perfil";
});
</script>
<template><p>Tab: {tab.value}</p></template>Common mistakes#
Compare the signal instead of its value#
<p if:when="route.path === '/about'">About</p>meeko check reports TS2367: This comparison appears to be unintentional because the types 'ReadonlySignal<string>' and 'string' have no overlap.
<p if:when="route.path.value === '/about'">About</p>A nonexistent route, or one without its params#
<a bind:aria-current="route.isActive('/nowhere')">Lost</a>
<a bind:aria-current="route.isActive('/users/[id]')">Profile</a>meeko check reports TS2345: Argument of type '"/nowhere"' is not assignable to parameter of type '"/" | "/about" | "/users/[id]"' for the first and TS2554: Expected 2 arguments, but got 1 for the second, because the dynamic route requires params.
<a bind:aria-current="route.isActive('/users/[id]', { params: { id: '7' } })">Profile</a>Use params without checking its type#
const id = route.params.value.id.toUpperCase();meeko check reports TS18046: 'route.params.value.id' is of type 'unknown': useRoute works for any route, so it does not know this route's schema. In the page, use its params props, typed by definePage; in a component, check the type:
const id = computed(() => {
const value = route.params.value.id;
return typeof value === "string" ? value : "";
});Call it in a unit test#
const html = renderToString({}); // the component calls useRoute()Throws Error: useRoute() necesita una aplicación de Meeko en ejecución: sólo funciona en componentes que renderiza el servidor del framework o que monta mountClientApp. En una prueba unitaria, pasa la ruta al componente como prop. Follow the message's advice:
const html = renderToString({ path: "/about" }); // the component receives the route as a propNotes#
- SSR. Values are those of the request, including
"prerender"and"stream"routes and layouts of a"client"route. - Hydration. The browser hydrates with the same values as SSR, even for HTML rendered for another URL (the
staticadapter's404.html, a prerendered page served with another query string). Immediately afterward,urlandpathbecome those of the address bar, with its fragment. - Navigation. Values change only when navigation commits, together with content, never while
useNavigation().pendingistrue. The previous page is disposed of before effects reading the route update. isActiveis reactive. It readspath, so the template orcomputedusing it updates on navigation. It returns the same result in SSR and hydration.- Encoding.
isActivecompares paths encoded likehref(), without query or fragment:/categoríasand/categor%C3%ADasare the same. - Page data. For the page's validated
paramsandsearch, its props are more precise. In<script server>, the full request is inusePage().