mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### 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 <[email protected]>
Co-authored-by: Claude <[email protected]>
28 lines
1.1 KiB
TypeScript
28 lines
1.1 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
|
|
describe('styled-jsx dynamic styles SSR', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
skipDeployment: true,
|
|
})
|
|
|
|
// Dynamic styled-jsx (with interpolated expressions) produces numeric class
|
|
// names at runtime via the DJB2 hash in styled-jsx's computeId function.
|
|
// This pattern matches production deployments where all jsx class names
|
|
// are numeric (e.g. jsx-2267428885) rather than hex (jsx-f36313d9f07883b7).
|
|
it('should contain dynamic styled-jsx styles during SSR', async () => {
|
|
const html = await next.render('/')
|
|
|
|
// Dynamic styled-jsx produces numeric class names at runtime
|
|
const numericClasses = html.match(/\bjsx-\d+\b/g) || []
|
|
console.log('Numeric jsx classes:', [...new Set(numericClasses)])
|
|
expect(numericClasses.length).toBeGreaterThan(0)
|
|
|
|
// All dynamic styles should be present as inline <style> tags
|
|
expect(html).toMatch(/color:.*?green/) // main page
|
|
expect(html).toMatch(/color:.*?blue/) // DynamicStyled
|
|
expect(html).toMatch(/background-color:.*?navy/) // header
|
|
expect(html).toMatch(/color:.*?purple/) // footer
|
|
})
|
|
})
|