Skip to content

fix(link): keep auto prefetch from fetching dynamic app routes#1177

Draft
NathanDrake2406 wants to merge 2 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/fix-link-prefetch-null
Draft

fix(link): keep auto prefetch from fetching dynamic app routes#1177
NathanDrake2406 wants to merge 2 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/fix-link-prefetch-null

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

Overview

Field Detail
Goal Stop App Router <Link prefetch={null}>, omitted prefetch, and prefetch=\"auto\" from behaving like prefetch={true} for dynamic routes.
Core change Split Link prefetch into disabled, auto, and full, then gate automatic full RSC prefetches through a generated static route manifest.
Main boundary prefetch={true} still opts into full RSC prefetch. null/undefined/\"auto\" no longer trigger full dynamic route payload fetches.
Primary files packages/vinext/src/shims/link.tsx, packages/vinext/src/entries/app-browser-entry.ts, packages/vinext/src/index.ts
Expected impact Less background work for dynamic App Router links that use default/auto prefetch semantics.

Why

The Link prefetch prop is not a boolean in the App Router contract. Next.js treats null, undefined, and \"auto\" as automatic prefetch, while true is the explicit full-prefetch mode. Vinext collapsed those states into “enabled”, which meant default links to dynamic routes could fetch full RSC payloads as soon as they entered the viewport.

Area Principle / invariant What this PR changes
Link API Public prop values must preserve the Next.js distinction between auto and full. Adds a typed prefetch mode resolver instead of using prefetch !== false.
App Router RSC prefetch Automatic full prefetch should only happen when vinext can prove it is a static route shape. Emits a small browser route manifest and matches hrefs against it before full auto prefetch.
Explicit opt-in prefetch={true} is still allowed to fetch the full route and data. Keeps explicit full prefetch behavior unchanged.

What changed

Scenario Before After
<Link href=\"/about\" /> for a static app route Full RSC prefetch Full RSC prefetch
<Link href=\"/blog/hello-world\" /> for a dynamic app route Full RSC prefetch No full auto RSC prefetch
<Link href=\"/blog/hello-world\" prefetch={true} /> Full RSC prefetch Full RSC prefetch
Dangerous URL schemes Disabled Disabled
Maintainer review path
  1. packages/vinext/src/shims/link.tsx: mode resolution, static route matching, and the auto-prefetch gate.
  2. packages/vinext/src/entries/app-browser-entry.ts: browser route manifest generation.
  3. packages/vinext/src/index.ts: passes scanned App Router routes into the browser entry generator.
  4. tests/link.test.ts and tests/entry-templates.test.ts: focused behavior and codegen coverage.
Validation
  • vp test run tests/link.test.ts tests/entry-templates.test.ts
  • vp check tests/link.test.ts tests/entry-templates.test.ts packages/vinext/src/shims/link.tsx packages/vinext/src/entries/app-browser-entry.ts packages/vinext/src/client/vinext-next-data.ts packages/vinext/src/index.ts
  • git diff --check
Risk / compatibility
  • Public API: widens the shim type to accept prefetch=\"auto\" | null, matching App Router Link.
  • Runtime: static App Router links still auto-prefetch full RSC payloads. Dynamic App Router links require prefetch={true} for full prefetch.
  • Build output: app browser entry now includes a compact route-shape manifest for page routes.
  • Intentional scope: Next.js can prefetch loading-boundary shells for some dynamic routes. Vinext currently caches complete RSC responses, not partial segment prefetches, so this PR avoids pretending auto can safely do that work.
Non-goals
  • Implement Next.js segment-cache or loading-boundary partial prefetches.
  • Include generated static param values in the browser prefetch manifest.
  • Change router.prefetch(), which is already an explicit imperative prefetch API.

References

Reference Why it matters
Next.js App Router Link prefetch prop Documents \"auto\", null, and undefined as automatic App Router prefetch, distinct from true.
Next.js prefetch mode mapping Maps true to full prefetch and null/\"auto\" to automatic/PPR behavior.
Next.js PrefetchKind docs States that auto prefetches static pages fully and dynamic pages partially.
Next.js prefetch=\"auto\" test Confirms \"auto\" is an alias for the default automatic behavior.

App Router Link treated null, undefined, and auto prefetch as the same full RSC prefetch used by prefetch={true}. That violates Next.js's public prefetch contract and over-fetches dynamic route payloads as soon as links enter the viewport.

The broken assumption was that prefetch only needed a boolean enabled state. Split Link prefetch into disabled, auto, and full modes, emit a small browser route manifest, and allow automatic full RSC prefetch only for matched static app routes while preserving explicit full prefetch.

Add focused tests for the mode mapping and generated route manifest.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 12, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1177

commit: 9374d66

The interception-context E2E asserts that full RSC prefetch cache keys stay separate by source context. After auto prefetch stops full-fetching dynamic routes, that fixture needs to request the full prefetch mode explicitly.
@NathanDrake2406 NathanDrake2406 marked this pull request as draft May 12, 2026 04:56
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