LangENKO

Client Router

useRouter, useParams, Link, NavLink — the SPA navigation hooks.

since v0.22
On this page

Client Router

Inside islands and "use client" pages, Mandu's client router gives you hooks and components for SPA-style navigation. It's the client-side companion to the file-system router that picks which page.tsx to render.

All exports from @mandujs/core/client.

useRouter()

Imperative navigation + router state.

"use client";
import { useRouter } from "@mandujs/core/client";

export default function SettingsForm() {
  const router = useRouter();

  async function onSubmit(data: FormData) {
    await saveSettings(data);
    router.push("/account"); // client-side nav (no reload)
  }

  return <form action={onSubmit}>...</form>;
}

Available methods:

Method Effect
router.push(href) Navigate, push history entry
router.replace(href) Navigate, replace history entry
router.back() Browser back
router.refresh() Re-run the server render for the current URL
router.pathname Current pathname
router.query Parsed query string

useParams()

Read the dynamic segments of the current route.

"use client";
import { useParams } from "@mandujs/core/client";

// Route: app/posts/[id]/comments.island.tsx
export default function Comments() {
  const { id } = useParams<{ id: string }>();
  // ...
}

For catch-all routes ([...slug]), the param is a string array.

Declarative navigation. Intercepts clicks, prefetches on hover, falls back to a full-page navigation for external URLs.

import { Link } from "@mandujs/core/client";

<Link href="/docs/start/quickstart">Quickstart</Link>
<Link href="/docs" prefetch="hover">Docs home</Link>
<Link href="https://github.com/konamgil/mandu" target="_blank">GitHub</Link>

Props:

Prop Values Default
href Absolute path or external URL
prefetch "hover" | "visible" | "eager" | false "hover"
replace boolean — use replaceState instead of pushState false
scroll boolean — scroll to top after nav true

Same as <Link> but adds an active class (or style) when the current URL matches.

import { NavLink } from "@mandujs/core/client";

<NavLink
  href="/docs/start"
  activeClass="text-primary font-bold"
  matchMode="startsWith"
>
  Start
</NavLink>

matchMode:

  • "exact" (default) — active only on exact pathname match
  • "startsWith" — active for any nested route (useful for top-level nav)

Low-level primitives — usually you want useRouter or <Link>, but:

import { navigate, prefetch, subscribe } from "@mandujs/core/client";

// Programmatic nav outside React (e.g. from an event handler)
navigate("/dashboard");

// Warm the cache
prefetch("/docs/architect");

// React to every navigation
const unsub = subscribe((url, { direction }) => {
  console.log("navigated to", url, direction);
});

subscribe is how smooth navigation triggers page transitions.

Where you can use these

  • Inside .island.tsx files (must start with "use client")
  • Inside "use client" pages
  • NOT inside server components — they throw at render time

For server-side redirects, use the Filling ctx.redirect() helper instead.

🤖 Agent Prompt

🤖 Agent Prompt — Client Router
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/reference/client-router to my project.

Summary of the page:
Mandu's client router (from @mandujs/core/client) mirrors React Router: useRouter for imperative navigation, useParams for route params, Link for declarative nav, NavLink for active-state links. Use inside islands or 'use client' pages.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

For Agents

AI hint

Mandu's client router (from @mandujs/core/client) mirrors React Router: useRouter for imperative navigation, useParams for route params, Link for declarative nav, NavLink for active-state links. Use inside islands or 'use client' pages.

Guard scope
client