hono-preact
Overview
Quick Start
The Route Table
Layouts & Nesting
Adding Pages
Active Links
Server Loaders
Loading States
Reloading Data
Prefetching
Streaming
Live Loaders
Realtime Channels
Server Actions
Validation
Optimistic UI
View Transitions
Middleware
CSRF Protection
CLI
Vite Config
Project Structure
Composing Hono Middleware
WebSockets
Rooms & Presence
renderPage
Link Prefetch
Build & Deploy
Overview
Dialog
Popover
Tooltip
Menu
Context Menu
Select
Combobox
Toast
renderElement
useControllableState
mergeRefs
useListNavigation
useTypeahead
useListboxSelection
usePosition
usePositioner
useDismiss
useFocusReturn
useSafeArea
usePresence

Link Prefetch#

Link prefetch makes navigation feel instant by fetching destinations before the user clicks. Enable it once with speculation: true in your app config; the framework emits a Speculation Rules tag that tells supporting browsers to prefetch same-origin links on hover. The feature is off by default.

Enabling#

Set speculation: true on your defineApp config:

import { defineApp } from 'hono-preact';

export default defineApp({
  speculation: true,
});

Once enabled, every server-rendered page emits the speculation rules tag. No per-route configuration.

The tag lands inside the existing <head> element. If your Layout renders an HTML document with no <head>, the framework has nowhere to inject the tag and no tag is emitted; the same constraint applies to hoofd's <title>, <meta>, and <link> injection.

What gets prefetched#

The emitted rule applies to every same-origin <a href> link rendered in the page. Cross-origin links are skipped automatically.

The browser decides when to prefetch based on the moderate eagerness setting: pointer hover or touchstart usually triggers it, click-time is too late. The prefetched response counts as a normal GET against your server.

Some links should never be prefetched: ones that mutate server state via a GET (logout, sign-out, unsubscribe), generate signed URLs that count against a quota, or otherwise have side effects on fetch. Mark them with the data-no-prefetch attribute:

<a href="/logout" data-no-prefetch>
  Sign out
</a>

The browser excludes any link matching [data-no-prefetch] from the rule. Plain HTML attribute; works on any <a> element.

Auditing before enabling#

The framework cannot tell which of your GET routes are safe to prefetch. Before flipping speculation: true on, review your app for routes where a GET does any of:

  • Records a write (analytics ping, view-counter increment, audit log).
  • Burns a one-shot token (signed-URL with single-use semantics).
  • Triggers a side effect (sends an email, decrements a quota, expires a session).

Add data-no-prefetch to the links that lead to those routes, or refactor them to POST so prefetch never fires. The framework's mutation pattern is POST-only by design; route-level GETs are expected to be idempotent.

Strict-CSP apps#

The emitted script is a normal <script> element and is subject to your Content-Security-Policy. Apps with strict CSPs that don't allow inline scripts will see the speculation script blocked by the browser. The framework does not currently plumb a CSP nonce. If your app needs Speculation Rules under strict CSP, file an issue describing the use case.

Prefetch on intent#

usePrefetch(href, loaders) returns a callback that prefetches a link's loader data, resolving the target route's params from the route table for you (no copied route pattern). Bind it to whatever events express intent, hover and focus, touch, an IntersectionObserver, a long-press:

import { usePrefetch } from 'hono-preact';
import { serverLoaders } from '../pages/issue.server.js';

function IssueLink({ href }: { href: string }) {
  const prefetchIssue = usePrefetch(href, serverLoaders.issue);
  return (
    <a href={href} onMouseEnter={prefetchIssue} onFocus={prefetchIssue}>
      Open issue
    </a>
  );
}

Pass one loader or an array. A warm cache makes repeat fires free, so binding to several events is fine.

See also#

  • Middleware: defineApp accepts other options beyond speculation, including use for middleware composition.
  • renderPage: how the appConfig (including speculation) is threaded into the server render.