Navigating
Navigating in the Routing and data guide.
Use A for links. It renders a real <a href>, navigates in the browser on a plain click, leaves modified clicks,
target, download and external URLs to the browser, sets aria-current="page" on the active link, and preloads its
target. useNavigate() and useRouter().navigate() navigate from code and resolve with an outcome (they never
throw for a redirect or not-found); <Navigate> navigates when it renders in the browser. useNavigation() exposes
the navigation in flight for a global spinner. All of them are described in Routing.
Preloading. A preloads on hover (after 50 ms), focus and touch (preload="intent", the default), when it scrolls
into view (preload="viewport") or never (preload={false}). On top of that, the client entry that Dusk generates
(hydrateDusk) preloads the loader data of any same-origin anchor the pointer reaches, focus enters or a touch starts
on, once per URL, unless the anchor has a data-no-prefetch attribute or hydrateDusk({ prefetch: false }). The
loader cache keeps a result for staleTime (0 by default: a navigation always refetches; a preload counts for
preloadStaleTime, 30 s). Set cache and timing options by writing your own src/entry-client.ts that calls
hydrateDusk({ routes, App, preload, router: { cache: { staleTime: 30_000 }, pendingMs: 200 } }).
Pending UI. A pending export is rendered when a client navigation has waited pendingMs for a loader. The
server never shows it: it waits for the loaders before sending the page. Error UI. An error export wraps the
route. Its reset prop rebuilds the route without loading the data again (a render that failed); its retry prop
revalidates the page's loaders first (a load that failed); the next navigation resets it either way. An error in the
first render of a streamed page surfaces before the first byte, so the status is the right one.
Error digest. In production the error UI never receives the message of an unexpected error: it receives the
public error (the reason phrase), and that error's digest: a short code (8 hexadecimal characters) for the
original error. The server's log line for the same error prints the same digest, so a user who quotes the code can be
matched to the log. The digest is a keyed hash, random per server process: it reveals nothing about the message, and
it changes when the process restarts. It is for correlation only, not a secret. In development the error UI shows
the message itself. The digest travels with the hydration data, so the client's error UI shows the same code.
import type { RouteErrorProps } from "@shadow-js/router";
export function error({ error: failure, retry }: RouteErrorProps) {
const digest = failure instanceof Error && "digest" in failure ? String(failure.digest) : "none";
return (
<section>
<h1>Something went wrong</h1>
<p>Reference: {digest}</p>
<button type="button" onClick={retry}>Try again</button>
</section>
);
}