Skip to main content
<Link> renders a plain <a> server-side, so it works with JavaScript off, on slow links, and during the brief moment before hydration completes. Once Pylon’s runtime is live, it intercepts clicks and does a client-side navigation instead of a full page reload.

What it does

The two stages differ because of cost. Chunks are content-hashed and served as immutable, so warming one costs its bytes once per browser and makes every later visit instant. It is cheap enough to do as soon as a link appears. The page payload costs a server render every time, so it waits for a real signal of intent. Warming payloads on sight would spend one render per visible link on every page load and throw away nearly all of them. The payload is held in memory only, never in a disk cache, so a reload always re-renders. It is single-use and short-lived, and it is dropped when a navigation commits. The React root persists across navigations, so a layout shared between the old and new route keeps its instance. State, scroll position, and video playback all survive the navigation.
A page that takes longer than ~100ms to arrive shows the nearest loading.tsx over the page area while it loads. Faster navigations swap straight to content, so the skeleton never flashes.

API

Any other anchor prop, such as className, target, rel, or onClick, passes through.

Examples

Basic

Disable prefetch

prefetch={false} turns off both stages. Use it when a link is unlikely to be followed and you do not want to warm anything for it:
You rarely need it for weight. A heavy destination is best fixed at the source with dynamic(), which keeps the heavy component out of the route’s chunk entirely. prefetch={false} only delays the download until the click, where the user is waiting for it.

Open in a new tab

Modifier keys and middle-click also fall through automatically. No special handling is needed.

Programmatic navigation

<Link> is the prop-based API. For imperative navigation, for example after a form submit or a post-login redirect, call the global runtime:
The runtime loads as part of the shared chunk. window.__pylon is defined as soon as hydration completes. Use the ?. form to stay safe on the first render. Pylon has no RSC boundary, so there is no "use client" directive. Every page component already hydrates on the client.

How navigation works

  1. The user clicks a <Link>. The runtime’s delegated handler listens for a[data-pylon-link] clicks.
  2. The runtime uses the payload a hover already fetched, or it fetches the target URL now. Either way, the request carries X-Pylon-Nav: 1, which tells the server this is a navigation, not a document load.
  3. The server still runs the render. This is what resolves the page’s serverData reads. But the server replies with only the head metadata and the __PYLON_DATA__ JSON (component, layouts, props, data), not the rendered markup, since the client re-renders from that data anyway. This is roughly half the bytes of a full page on a typical route.
  4. The runtime looks up the new component in the bundle manifest and dynamically imports its entry chunk.
  5. The entry chunk’s hydrate(component, Page, Layouts) populates the route cache without re-rendering (the cache is keyed on component path).
  6. The runtime calls root.render(buildTree(Page, Layouts, newProps)). React’s reconciler diffs against the existing tree. It reuses shared layouts and swaps only the page subtree.
  7. The runtime calls history.pushState and scrolls to top. Navigation is done.
If anything fails, for example the fetch errored, the response was not HTML, or the manifest did not know the component, the runtime falls back to window.location.href = href. The browser then handles the navigation as a normal document load.

Differences from Next.js’s <Link>

The hash-link and scroll props are on the roadmap.