Skip to content

fix(ssr): first request after server start always returns 500 #256

@gagipro

Description

@gagipro

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:

  1. handleSsr calls createFromReadableStream to create the React element tree from the RSC stream
  2. renderToReadableStream attempts to render the HTML shell synchronously
  3. When React encounters a client component reference, it calls __vite_rsc_client_require__(id) which returns a Promise (the async import() has not completed yet)
  4. Since there is no <Suspense> boundary wrapping the root shell, React SSR cannot render a fallback — it rejects with undefined
  5. The onError callback receives undefined, and renderToReadableStream rejects
  6. 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

  1. Build a vinext app with any "use client" components: vinext build
  2. Start the production server: vinext start
  3. Send a request immediately: curl http://localhost:3120/
  4. Result: 500 Internal Server Error
  5. Send the same request again: curl http://localhost:3120/
  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions