Files
Tobias Koppers 985394417f Fix styled-jsx race condition: styles lost due to concurrent rendering (#92459)
### What?

Fix a race condition in the Pages Router SSR path where styled-jsx
styles were dropped from the rendered HTML.

### Why?

`styledJsxInsertedHTML()` reads and flushes the styled-jsx style
registry. Previously it was called concurrently with the page render via
`Promise.all`:

```js
const [rawStyledJsxInsertedHTML, content] = await Promise.all([
  renderToString(styledJsxInsertedHTML()),
  (async () => { /* render the page */ })(),
])
```

Because both ran at the same time, `styledJsxInsertedHTML()` could (and
in practice did) execute and flush the registry **before** the page
render had finished populating it. The result was that dynamic
styled-jsx styles — those with interpolated expressions that compute
their class names at runtime via DJB2 hashing — were silently dropped
from the SSR output, causing a flash of unstyled content on first load.

This is particularly visible in production deployments where all
components use dynamic styled-jsx (numeric `jsx-*` class names), since
those styles only exist in the registry after rendering completes.

### How?

Serialize the two operations: render the page first, then call
`styledJsxInsertedHTML()`. Since the registry is fully populated by the
time it is read, all styles are captured correctly.

A new e2e test (`test/e2e/styled-jsx-dynamic`) exercises this scenario
with multiple nested components that all use dynamic styled-jsx with
interpolated props, covering the exact FOUC pattern seen in production.

<!-- NEXT_JS_LLM_PR -->

Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-07 17:02:37 +02:00

15 lines
263 B
JavaScript

export default function Header({ bg, fg }) {
return (
<header>
<style jsx>{`
header {
background-color: ${bg};
color: ${fg};
padding: 1rem;
}
`}</style>
<span>Header</span>
</header>
)
}