-
Notifications
You must be signed in to change notification settings - Fork 6
add RSC prefetch on Link hover #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'spiceflow': patch | ||
| --- | ||
|
|
||
| Add RSC prefetching on Link hover. The `Link` component now fetches the RSC payload when the user hovers (with 80ms debounce), focuses, or touches a link. Cached responses are used on navigation, making client-side page transitions feel instant. Prefetch is enabled by default and can be disabled with `<Link prefetch={false}>`. A `prefetchRoute(href)` function is also exported from `spiceflow/react` for programmatic use. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // Prefetch cache for RSC payloads. Link triggers prefetchRoute() on hover, | ||
| // the navigation handler in entry.client.tsx consumes cached responses via consumePrefetch(). | ||
| 'use client' | ||
|
|
||
| const PREFETCH_TTL = 5_000 | ||
|
|
||
| type PrefetchEntry = { | ||
| promise: Promise<Response> | ||
| timestamp: number | ||
| } | ||
|
|
||
| const cache = new Map<string, PrefetchEntry>() | ||
|
|
||
| export function prefetchRoute(href: string) { | ||
| if (typeof window === 'undefined') return | ||
|
|
||
| let url: URL | ||
| try { | ||
| url = new URL(href, window.location.origin) | ||
| } catch { | ||
| return | ||
| } | ||
| if (url.origin !== window.location.origin) return | ||
|
|
||
| url.searchParams.set('__rsc', '') | ||
| const key = url.pathname + url.search | ||
|
|
||
| if (cache.has(key)) return | ||
|
|
||
| const promise = fetch(url.toString()).catch(() => { | ||
| cache.delete(key) | ||
| return new Response(null, { status: 0 }) | ||
|
Comment on lines
+30
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the background prefetch fails (for example, the user is offline or the server restarts), this catch block throws a Useful? React with 👍 / 👎. |
||
| }) | ||
|
|
||
| cache.set(key, { promise, timestamp: Date.now() }) | ||
| } | ||
|
|
||
| export function consumePrefetch( | ||
| pathname: string, | ||
| search: string, | ||
| ): Promise<Response> | null { | ||
| const key = pathname + search | ||
| const entry = cache.get(key) | ||
| if (!entry) return null | ||
| cache.delete(key) | ||
| if (Date.now() - entry.timestamp > PREFETCH_TTL) return null | ||
| return entry.promise | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a hover/focus/touch prefetch is available, navigation skips
fetchFlightResponse()and feeds the cachedResponsestraight intocreateFromFetch(). That bypasses the logic in this file that hard-reloads on deployment-mismatch409s, 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 👍 / 👎.