[]previous and next page

Navigating

Navigating in the Routing and data guide.

For agentsUpdated

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.

src/routes/checkout.tsxtsx
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>
  );
}