[PR #1067] [MERGED] perf(app-router): coalesce client reference preloads #1064

Closed
opened 2026-05-06 13:11:48 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1067
Author: @NathanDrake2406
Created: 5/5/2026
Status: Merged
Merged: 5/5/2026
Merged by: @james-elicx

Base: mainHead: nathan/client-ref-preload-singleflight


📝 Commits (1)

  • 3ec59f4 perf(app-router): coalesce client reference preloads

📊 Changes

4 files changed (+236 additions, -32 deletions)

View changed files

📝 packages/vinext/src/global.d.ts (+9 -0)
packages/vinext/src/server/app-client-reference-preloader.ts (+100 -0)
📝 packages/vinext/src/server/app-ssr-entry.ts (+15 -32)
tests/app-client-reference-preloader.test.ts (+112 -0)

📄 Description

What this changes

App Router SSR now shares a single in-flight client-reference preload during cold starts instead of letting concurrent first requests each iterate the full virtual:vite-rsc/client-references manifest and allocate their own Promise.all fan-out.

The preload logic moved out of app-ssr-entry.ts into a typed helper that:

  • coalesces the global manifest preload promise
  • dedupes work per client-reference id
  • keeps completed references on a zero-allocation fast path
  • preserves the existing non-fatal dev warning behavior for individual preload failures
  • exposes a scoped preload(referenceIds) shape for a future route-level manifest without losing cross-route per-id dedupe

Why

The existing preload is required for correctness: first-request SSR can fail if React consumes the Flight stream before Vite RSC client references have resolved. But the state only tracked completion. During a cold burst, every request entering before the boolean flipped repeated manifest key iteration and promise fan-out even though Vite RSC memoized the underlying imports.

This keeps the correctness barrier and removes the duplicate orchestration work.

Approach

handleSsr still awaits client-reference preload before creating the SSR tree. The difference is that it now delegates to createClientReferencePreloader, which owns the concurrency contract directly and is covered by focused unit tests.

I did not synthesize a route-to-client-reference manifest in this PR. @vitejs/plugin-rsc currently owns the reliable client-reference graph behind private build metadata, while the public virtual module is app-global. Guessing that graph from transformed module IDs would be fragile for package client references and production hash keys.

The new helper is intentionally shaped so a future generated route manifest can pass scoped ids once Vinext has trustworthy build metadata. That likely lines up with the RouteManifest/resource-dependency pieces of #726, not the entire #726 roadmap: once Vinext has authoritative route-to-module facts, SSR can call preload(routeClientReferenceIds) for the matched route while retaining the same per-id dedupe and correctness barrier introduced here.

Validation

  • vp check packages/vinext/src/server/app-client-reference-preloader.ts packages/vinext/src/server/app-ssr-entry.ts packages/vinext/src/global.d.ts tests/app-client-reference-preloader.test.ts
  • vp test run tests/app-client-reference-preloader.test.ts
  • vp test run tests/app-client-reference-preloader.test.ts tests/app-router.test.ts
  • vp run vinext#build

Risks / follow-ups

The preloader intentionally treats individual client-reference preload failures as reported-but-complete, matching the previous best-effort behavior in app-ssr-entry.ts. A future route-scoped manifest should be generated from authoritative RouteManifest/resource-dependency metadata from #726, not from source filename heuristics or Flight stream parsing.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1067 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/5/2026 **Status:** ✅ Merged **Merged:** 5/5/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `nathan/client-ref-preload-singleflight` --- ### 📝 Commits (1) - [`3ec59f4`](https://github.com/cloudflare/vinext/commit/3ec59f499ce778d0004c818ed8228a6d43c05d95) perf(app-router): coalesce client reference preloads ### 📊 Changes **4 files changed** (+236 additions, -32 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/global.d.ts` (+9 -0) ➕ `packages/vinext/src/server/app-client-reference-preloader.ts` (+100 -0) 📝 `packages/vinext/src/server/app-ssr-entry.ts` (+15 -32) ➕ `tests/app-client-reference-preloader.test.ts` (+112 -0) </details> ### 📄 Description ## What this changes App Router SSR now shares a single in-flight client-reference preload during cold starts instead of letting concurrent first requests each iterate the full `virtual:vite-rsc/client-references` manifest and allocate their own `Promise.all` fan-out. The preload logic moved out of `app-ssr-entry.ts` into a typed helper that: - coalesces the global manifest preload promise - dedupes work per client-reference id - keeps completed references on a zero-allocation fast path - preserves the existing non-fatal dev warning behavior for individual preload failures - exposes a scoped `preload(referenceIds)` shape for a future route-level manifest without losing cross-route per-id dedupe ## Why The existing preload is required for correctness: first-request SSR can fail if React consumes the Flight stream before Vite RSC client references have resolved. But the state only tracked completion. During a cold burst, every request entering before the boolean flipped repeated manifest key iteration and promise fan-out even though Vite RSC memoized the underlying imports. This keeps the correctness barrier and removes the duplicate orchestration work. ## Approach `handleSsr` still awaits client-reference preload before creating the SSR tree. The difference is that it now delegates to `createClientReferencePreloader`, which owns the concurrency contract directly and is covered by focused unit tests. I did not synthesize a route-to-client-reference manifest in this PR. `@vitejs/plugin-rsc` currently owns the reliable client-reference graph behind private build metadata, while the public virtual module is app-global. Guessing that graph from transformed module IDs would be fragile for package client references and production hash keys. The new helper is intentionally shaped so a future generated route manifest can pass scoped ids once Vinext has trustworthy build metadata. That likely lines up with the RouteManifest/resource-dependency pieces of #726, not the entire #726 roadmap: once Vinext has authoritative route-to-module facts, SSR can call `preload(routeClientReferenceIds)` for the matched route while retaining the same per-id dedupe and correctness barrier introduced here. ## Validation - `vp check packages/vinext/src/server/app-client-reference-preloader.ts packages/vinext/src/server/app-ssr-entry.ts packages/vinext/src/global.d.ts tests/app-client-reference-preloader.test.ts` - `vp test run tests/app-client-reference-preloader.test.ts` - `vp test run tests/app-client-reference-preloader.test.ts tests/app-router.test.ts` - `vp run vinext#build` ## Risks / follow-ups The preloader intentionally treats individual client-reference preload failures as reported-but-complete, matching the previous best-effort behavior in `app-ssr-entry.ts`. A future route-scoped manifest should be generated from authoritative RouteManifest/resource-dependency metadata from #726, not from source filename heuristics or Flight stream parsing. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:48 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1064
No description provided.