[PR #257] [MERGED] fix(ssr): preload client reference modules before first SSR render #421

Closed
opened 2026-05-06 12:39:44 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/257
Author: @gagipro
Created: 3/5/2026
Status: Merged
Merged: 3/16/2026
Merged by: @james-elicx

Base: mainHead: fix/preload-client-refs-ssr


📝 Commits (1)

  • 157b13f fix(ssr): preload client reference modules before first SSR render

📊 Changes

3 files changed (+122 additions, -0 deletions)

View changed files

📝 packages/vinext/src/entries/app-ssr-entry.ts (+26 -0)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+26 -0)
📝 tests/app-router.test.ts (+70 -0)

📄 Description

Summary

Fixes #256 — the first HTTP request after vinext start always returns 500.

Problem

On cold start, client reference modules ("use client" components) are loaded lazily via
async import() through @vitejs/plugin-rsc's setRequireModule({ load }). When handleSsr
runs renderToReadableStream on the first request, React SSR tries to render the HTML shell
but the client component imports haven't resolved yet. With no <Suspense> boundary wrapping
the root, React cannot render a fallback and rejects with undefined, causing a 500.

All subsequent requests work because memoize() in setRequireModule caches resolved modules.

Fix

Eagerly preload all client reference modules at the start of handleSsr, before
renderToReadableStream runs:

import * as _clientRefs from "virtual:vite-rsc/client-references";

export async function handleSsr(rscStream, navContext, fontData) {
  // Preload client refs so first SSR render can resolve them synchronously
  if (_clientRefs?.default && globalThis.__vite_rsc_client_require__) {
    await Promise.all(
      Object.keys(_clientRefs.default).map((id) =>
        globalThis.__vite_rsc_client_require__(id).catch?.(() => {})
      )
    );
  }
  // ... rest of handleSsr unchanged
}

The virtual:vite-rsc/client-references module (provided by @vitejs/plugin-rsc) exposes
the map of all client component IDs. The __vite_rsc_client_require__ global is already set
by initialize() before handleSsr is called.

Scope

  • 1 file changed, 16 insertions
  • packages/vinext/src/server/app-dev-server.ts — inside generateSsrEntry() template
  • No changes to @vitejs/plugin-rsc or any other package

Testing

Verified on a production vinext app with 11 client component references:

  • Before fix: first request after restart → 500 (100% reproducible)
  • After fix: 5/5 restart cycles → 200 on first request, all routes (/, /login, /dashboards, /datasources, /dashboards/[id]) return 200

Notes

  • The preload runs once per request but the memoize() cache means only the first call per ID
    does actual work. The overhead on subsequent requests is negligible (synchronous Map lookups).
  • The defensive ?.default and ?.catch guards ensure the fix is safe if @vitejs/plugin-rsc
    changes its export shape in the future.
  • This fix is in the generated SSR entry (template literal in generateSsrEntry()), so it
    affects both dev and production SSR.

🔄 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/257 **Author:** [@gagipro](https://github.com/gagipro) **Created:** 3/5/2026 **Status:** ✅ Merged **Merged:** 3/16/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/preload-client-refs-ssr` --- ### 📝 Commits (1) - [`157b13f`](https://github.com/cloudflare/vinext/commit/157b13f611c37e27660164dd3606879ec4a2bf2d) fix(ssr): preload client reference modules before first SSR render ### 📊 Changes **3 files changed** (+122 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/app-ssr-entry.ts` (+26 -0) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+26 -0) 📝 `tests/app-router.test.ts` (+70 -0) </details> ### 📄 Description ## Summary Fixes #256 — the first HTTP request after `vinext start` always returns 500. ## Problem On cold start, client reference modules (`"use client"` components) are loaded lazily via async `import()` through `@vitejs/plugin-rsc`'s `setRequireModule({ load })`. When `handleSsr` runs `renderToReadableStream` on the first request, React SSR tries to render the HTML shell but the client component imports haven't resolved yet. With no `<Suspense>` boundary wrapping the root, React cannot render a fallback and rejects with `undefined`, causing a 500. All subsequent requests work because `memoize()` in `setRequireModule` caches resolved modules. ## Fix Eagerly preload all client reference modules at the start of `handleSsr`, before `renderToReadableStream` runs: ```typescript import * as _clientRefs from "virtual:vite-rsc/client-references"; export async function handleSsr(rscStream, navContext, fontData) { // Preload client refs so first SSR render can resolve them synchronously if (_clientRefs?.default && globalThis.__vite_rsc_client_require__) { await Promise.all( Object.keys(_clientRefs.default).map((id) => globalThis.__vite_rsc_client_require__(id).catch?.(() => {}) ) ); } // ... rest of handleSsr unchanged } ``` The `virtual:vite-rsc/client-references` module (provided by `@vitejs/plugin-rsc`) exposes the map of all client component IDs. The `__vite_rsc_client_require__` global is already set by `initialize()` before `handleSsr` is called. ## Scope - **1 file changed**, 16 insertions - `packages/vinext/src/server/app-dev-server.ts` — inside `generateSsrEntry()` template - No changes to `@vitejs/plugin-rsc` or any other package ## Testing Verified on a production vinext app with 11 client component references: - **Before fix:** first request after restart → 500 (100% reproducible) - **After fix:** 5/5 restart cycles → 200 on first request, all routes (/, /login, /dashboards, /datasources, /dashboards/[id]) return 200 ## Notes - The preload runs once per request but the `memoize()` cache means only the first call per ID does actual work. The overhead on subsequent requests is negligible (synchronous Map lookups). - The defensive `?.default` and `?.catch` guards ensure the fix is safe if `@vitejs/plugin-rsc` changes its export shape in the future. - This fix is in the generated SSR entry (template literal in `generateSsrEntry()`), so it affects both dev and production SSR. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 12:39:44 +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#421
No description provided.