Skip to content

Authenticated App Router browser SSR calls legacy react-dom/server API (E394 InvariantError) — full-HTML render resolves streaming export to the poison stub #6710

Description

@proggeramlug

Summary

A logged-in user loading an authenticated App Router page in a real browser (full HTML SSR) crashes under Perry with Next.js E394Internal Error: do not use legacy react-dom/server APIs — repeated as unhandledRejection: InvariantError, and the response stream never cleanly closes (browser spins "loading forever"; curl reports 500). Node renders the identical request 200.

E394 is a Next.js poison-pill stub: function a(){ throw Error("Internal Error: do not use legacy react-dom/server APIs...") } assigned to the legacy react-dom/server exports (renderToString, renderToStaticMarkup, renderToNodeStream, …). App Router must only call the streaming APIs. So under Perry, a react-dom/server module export is resolving to the throwing legacy stub instead of the real streaming function during the browser SSR of an authenticated page.

Reproduction (gscmaster, Next 16.1.1 standalone)

request Perry Node
GET /de/dashboard with Accept: text/html + browser UA, authenticated 500 + E394/InvariantError flood 200
GET /de/dashboard plain curl (no Accept/UA), authenticated 200 (byte-identical to node) 200
GET /de/dashboard with RSC: 1 (client nav), authenticated 200 200
public pages with browser UA 200 200

So it is specific to the full-HTML SSR path of an authenticated, dynamic = "force-dynamic" page — exactly what a browser does on the post-login redirect. Plain curl and the RSC-only path take a different render route and succeed, which is why a 220-request curl soak passed while a real browser login hangs.

Likely in the react-dom/server (Fizz) HTML-shell render, possibly interacting with the PPR/postpone-resume path (the bundle also carries Invariant: isDynamicPostpone misidentified a postpone reason), cf. the module-export "value form ≠ static form" resolution family (#6667/#6668) and PPR resume #5437.

Next step

Wrap the bundle's E394 stub a() to log a stack when called → identify exactly which react-dom/server export is mis-resolved to the stub, then fix Perry's resolution of that export (probable: the streaming API being read as a value/namespace member and getting the legacy alias).

Observed on main @ 3fd379f, macOS arm64. Workaround unknown; the RSC/plain-fetch path is unaffected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regressionparityCompatibility gap with Node.js, ECMAScript, or the supported ecosystemtriagedMaintainer reviewed; type, scope, and next step are clear

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions