Reference

Links & Navigation

<Link> moves between pages in your app. It renders as a plain <a> on the server and once <ClientEntry> is mounted, upgrades same-origin clicks to client-side navigation. No full reload, the same route handler runs and returns a JSON payload instead of HTML. Without JavaScript it degrades to ordinary browser navigation.

import { Link } from "goribu";

<Link href="/polls/42">View poll</Link>;

For anything that should leave the app or use the browser's own navigation, use a plain <a>.

<NavLink> is a <Link> that knows when its href matches the current URL. It is the right tool for nav bars, sidebars, tabs and breadcrumbs. It sets aria-current="page" automatically when active, and lets className, style and children each be a function of { isActive }:

import { NavLink } from "goribu";

<NavLink
  href="/feed"
  className={({ isActive }) => (isActive ? "font-semibold text-blue-600" : "text-slate-600")}
>
  Feed
</NavLink>;

Matching is by prefix by default, so /dashboard is active on /dashboard/settings too. Pass end for an exact match:

<NavLink href="/dashboard" end>Overview</NavLink>

/ is always exact; query strings, hashes and trailing slashes are normalized away; external URLs are never active.

Read the current URL

useLocation() returns the current URL inside a component. On the server it reads the request URL; on the client it updates when an SPA navigation finishes.

import { useLocation } from "goribu";

function SearchQuery() {
  const { pathname, search, hash, searchParams } = useLocation();
  const q = searchParams.get("q") ?? "";
  return <p>Searching for “{q}”</p>;
}

searchParams is a fresh URLSearchParams so mutating it does not change the URL. For nav menus prefer <NavLink> but reach for useLocation() when you need the raw URL or the active-state model doesn't fit.

Make the next page feel instant

<Link> prefetches on hover and pointer-down by default, so the destination is usually ready before the click lands. It is only an optimization. If it hasn't finished, the navigation still works. Turn it off per link with prefetch={false}:

<Link href="/feed" prefetch={false}>Feed</Link>

Prerendered routes serve their navigation data from the CDN while dynamic routes prefetch through the Worker. So consider disabling it on very long lists or highly dynamic pages.

Replace history instead of stacking

Pass replace when the navigation should overwrite the current history entry rather than add one like after login, signup, redirects or multi-step flows where Back should not return to the previous step:

<Link href="/dashboard" replace>Dashboard</Link>

Going deeper

What gets intercepted

<Link> only intercepts same-origin clicks in the current tab. These fall through to the browser by design, and Goribu does not override them:

  • external URLs;
  • modified clicks (Cmd, Ctrl, Shift, Alt);
  • middle-clicks;
  • target="_blank" or any non-self target;
  • hash-only changes on the current page.

Anchor props pass through

<Link> is a thin wrapper around <a>, so standard anchor props pass straight through. Your event handlers run first, so if your onClick calls event.preventDefault(), Goribu does not intercept the click.

<Link href="/profile" className="font-medium" onClick={() => track("profile-click")}>
  Profile
</Link>

Where navigation components belong

<NavLink> and anything calling useLocation() must live inside your route component tree, not in document.jsx. The Document is the static app shell; it does not re-render on client navigation, so a <NavLink> placed there would never update its active state. Put shared navigation in a normal component and import it into the routes that use it.