Skip to content

feat(router): support View Transitions for Link navigation - #3208

Draft
NathanDrake2406 wants to merge 4 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/2603-view-transitions
Draft

feat(router): support View Transitions for Link navigation#3208
NathanDrake2406 wants to merge 4 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/2603-view-transitions

Conversation

@NathanDrake2406

@NathanDrake2406 NathanDrake2406 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Overview

Goal Support React View Transition animations and typed App Router Link navigation.
Runtime Pin workspace React, React DOM, RSC, and React types to 19.3.0.
Boundary Register transition types only inside the React transition that publishes an approved route.

Closes #2603.

Why

React 19.3 makes View Transitions stable. On current main, Link accepts transitionTypes but discards it. The compatibility report still describes a no-op fallback, and settled prefetches force synchronous commits that prevent React animations.

For example, <Link transitionTypes={['slide']}> must select the slide animation when its destination becomes visible. The navigation controller owns that commit. Registering types only in the Link click handler loses the React transition context across the asynchronous response.

The change follows the existing scroll-intent and visible-commit-mode plumbing in shims/navigation.ts and server/app-browser-entry.ts. Fresh, prefetched, cached, redirected, and optimistic candidates carry the types to server/app-browser-navigation-controller.ts.

What changed

Scenario Result
experimental.viewTransition: true Require matching React runtimes with native ViewTransition and addTransitionType capabilities.
Typed Link Register its types inside the approved commit transition.
Untyped Link Keep React's default transition behaviour.
Settled prefetch Use the transition lane when enabled; preserve explicit synchronous gesture mode.
Superseded response Existing approval and cancellation rules prevent stale animation.
Browser without View Transitions Navigation still completes.
Compatibility report Report native support when the installed runtime provides it.
Validation
  • 2,254 targeted tests passed across config, compatibility reports, navigation runtime/controller, Link, Form, shims, and dynamic requests.
  • 12 browser tests passed: development and production in Chrome and WebKit. Tests observe real document.startViewTransition() calls and types, check console/hydration errors, verify cached/prefetched reuse without another request, and hold a response until its navigation is superseded.
  • 11 existing CSP and metadata browser tests passed. They retain their strict policies and opt in only to React 19.3's exact development-only eval diagnostic.
  • vp check passed repository-wide formatting, lint, and type checks.
  • node scripts/sync-next-types.mjs --check and node scripts/check-shim-types.mjs passed.
  • vp run vinext#build passed.
  • pnpm install --frozen-lockfile --ignore-scripts passed.
  • Full PR CI passes at dc23c9e25. The View Transition development fixture now uses the existing child-server helper. This prevents a preceding production build from mixing cached production React with development JSX. All navigation assertions and hydration timeouts remain unchanged.
  • The failing test order (SSR recovery followed by View Transitions) passed locally in Chrome and WebKit without retries: 7 tests per browser.
  • The deploy fixture/compatibility regression tests from fix(test): pin React in deploy suite fixtures #3229 pass: 14 tests. No hosted Workers deployment was run.
Review path and compatibility
  1. utils/react-version.ts and the config define in index.ts establish the runtime requirement.
  2. server/app-browser-navigation-controller.ts owns type registration after approval.
  3. server/app-browser-entry.ts preserves types across candidates and keeps prefetched commits animatable.
  4. tests/e2e/app-router/view-transitions.browser.spec.ts proves browser behaviour.

The feature defaults off. Existing React peer ranges and older-runtime compatibility shims remain available. The workspace version update affects all fixtures and examples; most lockfile changes are React peer-resolution keys.

The React migration cost is isolated with three local controls on the same machine and generated 33-route benchmark. Main is 13e7d9ce5 (includes #3229). The React-only control changes only pnpm-workspace.yaml and pnpm-lock.yaml. The full feature is b7975b54e.

Gzip bytes Main / React 19.2 React 19.3 only Full feature React update delta Feature delta
Client bundle 145,749 154,565 154,582 +8,816 (+6.05%) +17
Client entry 132,726 141,545 141,556 +8,819 (+6.64%) +11
RSC entry closure 128,524 133,942 133,928 +5,418 (+4.22%) -14
Server bundle 221,315 227,311 227,303 +5,996 (+2.71%) -8

Each control used its frozen lockfile, the existing benchmarks/generate-app.mjs, and benchmarks/perf/run-scenarios.mjs --direct --rounds=3 with Next.js excluded. The harness performs three clean production builds, then measures the final output three times. All three size reads matched. View Transitions remain off in this benchmark, matching CI. With experimental.viewTransition: true, the same full-feature benchmark emits 154,604 client bytes (+39 over React-only), 141,580 client-entry bytes (+35), 133,939 RSC-entry bytes (-3), and 227,314 server bytes (+3). These local byte counts isolate the dependency update; they do not establish runtime animation performance. Sequential local timing samples are not used to claim a speed change.

The dependency update accounts for essentially all of the measured size increase. The final CI benchmark at dc23c9e25 reports production build time +1.6% and cold-start time -0.4% for the full change; those timings are not isolated by these size controls. Its acceptance remains a separate maintainer decision.

Deploy compatibility was run against Next.js v16.2.6, with the same manifest and zero assertion retries. Each control produced 799 result files covering the same 3,435 assertions.

Control Passed Failed Skipped / todo Run
Main / React 19.2 (13e7d9ce5) 2,681 123 631 main control
Main / React 19.3 only (0c5112a00) 2,671 133 631 React-only control
Full feature (b7975b54e) 2,670 134 631 feature control

Assertion-level comparison isolates twelve new failures to the React update, reproduced without any feature code:

  • Eight server-action error assertions across app-action.test.ts and app-action-node-middleware.test.ts, one global metadata-error assertion, and one process-taint assertion receive minified React error perf: og-inline-fetch-assets blocks Vite transform pipeline with sync I/O #441 instead of the expected production message.
  • Two unstable_catchError Server Component recovery tests time out waiting for the error-boundary element, with and without React Compiler. These remain recovery failures; they are not dismissed as text-only changes.
  • Two assertions that failed on main pass in both React 19.3 controls (optimistic route prediction and updateTag fetch-cache freshness). Single-run differences do not establish fixes.

The full feature differs from the React-only control in one hash-scroll assertion (navigation.test.ts, expected offset 2284, received 0). A focused navigation rerun at dc23c9e25 passed all 50 assertions, including that hash-scroll assertion, with no retries. The extra failure did not reproduce. That head changes only the browser-test process isolation after the measured feature head; production code and dependencies are identical.

Merge remains blocked on a deliberate React 19.3 compatibility decision. No deploy-suite expectations or support classifications were relaxed to hide these failures.

This implements the Link-specific contract in #2603. router.push/replace transition types remain a separate compatibility gap; this PR does not claim complete App Router View Transition parity.

Non-goals: adding router.push/replace transition types, changing gesture navigation semantics, bundling a Canary React channel, or implementing a separate browser-level transition shim.

References: React 19.3, Next.js View Transition fixtures.

Link navigation discards transition types, and settled prefetches bypass
React transitions. Carry types to the approved visible commit and keep
prefetched content in the transition lane when the feature is enabled.

Require matching capable React runtimes for experimental.viewTransition.
Pin the workspace React packages and types to 19.3.0, retain compatibility
with older runtimes when disabled, and report native capability accurately.

Cover real animations, cached navigation, missing browser support, and
superseded responses in development and production browser tests.
@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@3208
npm i https://pkg.pr.new/create-vinext-app@3208
npm i https://pkg.pr.new/@vinext/types@3208
npm i https://pkg.pr.new/vinext@3208

commit: dc23c9e

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared dc23c9e against base 13e7d9c using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 5 regressed · 1 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.4 KB 151.0 KB 🔴 +6.0%
Client entry size (gzip) vinext 129.7 KB 138.3 KB 🔴 +6.6%
Dev server cold start vinext 2.30 s 2.29 s ⚫ -0.4%
Production build time vinext 2.37 s 2.40 s 🔴 +1.6%
RSC entry closure size (gzip) vinext 125.4 KB 130.7 KB 🔴 +4.2%
Server bundle size (gzip) vinext 216.1 KB 222.0 KB 🔴 +2.7%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

Keep strict CSP tests intact while accepting only the new React development eval diagnostic through an explicit per-test option. Continue to fail unexpected console errors and all uncaught page errors.

Update Form navigation assertions for the optional transitionTypes argument. The dynamic-request test timeout did not reproduce locally.
Run the development fixture through the existing child-server helper so earlier production builds cannot leave a cached React production renderer paired with development JSX. Preserve every navigation assertion and the hydration timeout.
@NathanDrake2406 NathanDrake2406 changed the title feat(router): support React View Transition navigation feat(router): support View Transitions for Link navigation Sep 13, 2026
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.

App Router: support React ViewTransition animations and Link transitionTypes

1 participant