Skip to content

add RSC prefetch on Link hover#41

Open
remorses wants to merge 1 commit intomainfrom
link-prefetch-on-hover
Open

add RSC prefetch on Link hover#41
remorses wants to merge 1 commit intomainfrom
link-prefetch-on-hover

Conversation

@remorses
Copy link
Copy Markdown
Owner

This is a bit of a weird change and I'm not sure I want it. It prefetches the RSC payload when hovering over a <Link>, which makes navigations feel instant, but it can cause stale content since the prefetched response may be outdated by the time the user clicks (TTL is 5 seconds).

What changed:

  • Link fires a fetch(?__rsc) on hover (80ms debounce), focus, and touch start
  • Navigation handler in entry.client.tsx uses the cached response when available, falls back to fresh fetch on cache miss
  • New prefetch prop on Link (default true, set to false to disable)
  • prefetchRoute(href) exported from spiceflow/react for programmatic use

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 19, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
spiceflow-website-worker 3f859ce Mar 19 2026, 09:42 PM

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89a235bc82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +163 to +165
const cached = consumePrefetch(url.pathname, url.search)
const payload = createFromFetch<ServerPayload>(
fetchFlightResponse({ url, kind: 'navigation' }),
cached ?? fetchFlightResponse({ url, kind: 'navigation', init: { signal: navigationAbort.signal } }),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize cached prefetch responses before parsing

When a hover/focus/touch prefetch is available, navigation skips fetchFlightResponse() and feeds the cached Response straight into createFromFetch(). That bypasses the logic in this file that hard-reloads on deployment-mismatch 409s, followed redirects, and other non-Flight responses, so a prefetched click during a rollout (or any prefetched navigation that resolves to a document response) can fail inside the RSC parser instead of recovering with a browser navigation.

Useful? React with 👍 / 👎.

Comment on lines 39 to 42
export function stripRscUrl(url: URL) {
const next = new URL(url.href)
if (next.pathname.endsWith('.rsc')) {
next.pathname = next.pathname.slice(0, -4)
}
next.searchParams.delete('__rsc')
return next
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep stripping .rsc when deriving reload locations

During a rolling deploy, tabs still running the previous client bundle continue to request URLs like /page.rsc?__rsc=. getDocumentPath() now preserves that .rsc suffix, so the deployment-mismatch recovery header points the browser at /page.rsc instead of /page; combined with the removed pathname normalization in handle(), the fallback reload lands on the wrong resource rather than recovering the navigation.

Useful? React with 👍 / 👎.

Comment on lines +30 to +32
const promise = fetch(url.toString()).catch(() => {
cache.delete(key)
return new Response(null, { status: 0 })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid constructing an invalid fallback Response on prefetch errors

If the background prefetch fails (for example, the user is offline or the server restarts), this catch block throws a RangeError because new Response(..., { status: 0 }) is invalid in the Fetch API. That turns a harmless cache miss into an unhandled rejection, and a click that consumes this cached promise can fail instead of falling back to a fresh navigation request.

Useful? React with 👍 / 👎.

@remorses remorses force-pushed the link-prefetch-on-hover branch from 4783aaf to 15f22a3 Compare March 19, 2026 21:41
Link now prefetches the RSC payload on hover (80ms debounce), focus, and
touch start. The navigation handler in entry.client.tsx consumes cached
responses when available, falling back to a fresh fetch on cache miss.

New prefetch cache module (prefetch.ts) stores Promise<Response> keyed by
pathname+search with a 5 second TTL. Cache entries are consumed (deleted)
on navigation so they are never reused twice.

New Link prop: prefetch (default true, set false to disable).
New export: prefetchRoute(href) from spiceflow/react for programmatic use.
@remorses remorses force-pushed the link-prefetch-on-hover branch from 15f22a3 to 3f859ce Compare March 19, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant