-
Notifications
You must be signed in to change notification settings - Fork 210
Description
Bug Description
The first HTTP request after starting the vinext production server (vinext start) always returns a 500 Internal Server Error. All subsequent requests succeed with 200. This is 100% reproducible on every server restart.
Root Cause
During production SSR, client reference modules ("use client" components) are loaded lazily via async import() through the setRequireModule({ load }) mechanism provided by @vitejs/plugin-rsc. On the very first request:
handleSsrcallscreateFromReadableStreamto create the React element tree from the RSC streamrenderToReadableStreamattempts to render the HTML shell synchronously- When React encounters a client component reference, it calls
__vite_rsc_client_require__(id)which returns a Promise (the asyncimport()has not completed yet) - Since there is no
<Suspense>boundary wrapping the root shell, React SSR cannot render a fallback — it rejects withundefined - The
onErrorcallback receivesundefined, andrenderToReadableStreamrejects - The production server catches this as
"Server error: undefined"and returns 500
On subsequent requests, the modules are already cached by the memoize() wrapper in setRequireModule, so __vite_rsc_client_require__ resolves synchronously from cache and SSR succeeds.
Steps to Reproduce
- Build a vinext app with any
"use client"components:vinext build - Start the production server:
vinext start - Send a request immediately:
curl http://localhost:3120/ - Result: 500 Internal Server Error
- Send the same request again:
curl http://localhost:3120/ - Result: 200 OK
This reproduces on every restart cycle — systemctl restart / vinext start / any cold start.
Environment
- vinext: v0.0.20
- @vitejs/plugin-rsc: v0.5.21
- Node.js: v22.x
- OS: Linux (Ubuntu 24.04)
Proposed Fix
Eagerly preload all client reference modules at the start of handleSsr, before renderToReadableStream runs. The virtual:vite-rsc/client-references module (provided by @vitejs/plugin-rsc) exposes the map of all client component IDs to their async import functions. By calling __vite_rsc_client_require__ for each ID before rendering, the memoize cache is warmed and all subsequent require calls during SSR resolve synchronously.
The fix is minimal (16 lines, single file) and has no impact on subsequent requests since memoize() returns cached values immediately.
PR incoming: #TBD