Skip to content

Commit 6c86578

Browse files
committed
inject initial styles to end of head, add test
1 parent d289a2b commit 6c86578

File tree

5 files changed

+28
-12
lines changed

5 files changed

+28
-12
lines changed

packages/next/server/app-render.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,7 @@ export async function renderToHTMLOrFlight(
10521052
dataStream: serverComponentsInlinedTransformStream?.readable,
10531053
generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures,
10541054
flushEffectHandler,
1055+
flushEffectsToHead: true,
10551056
initialStylesheets,
10561057
})
10571058
}

packages/next/server/node-web-streams-helper.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export function createFlushEffectStream(
137137
}
138138

139139
export function createHeadInjectionTransformStream(
140-
inject: string
140+
inject: () => string
141141
): TransformStream<Uint8Array, Uint8Array> {
142142
let injected = false
143143
return new TransformStream({
@@ -147,7 +147,7 @@ export function createHeadInjectionTransformStream(
147147
if (!injected && (index = content.indexOf('</head')) !== -1) {
148148
injected = true
149149
const injectedContent =
150-
content.slice(0, index) + inject + content.slice(index)
150+
content.slice(0, index) + inject() + content.slice(index)
151151
controller.enqueue(encodeText(injectedContent))
152152
} else {
153153
controller.enqueue(chunk)
@@ -175,12 +175,14 @@ export async function continueFromInitialStream(
175175
dataStream,
176176
generateStaticHTML,
177177
flushEffectHandler,
178+
flushEffectsToHead,
178179
initialStylesheets,
179180
}: {
180181
suffix?: string
181182
dataStream?: ReadableStream<Uint8Array>
182183
generateStaticHTML: boolean
183184
flushEffectHandler?: () => string
185+
flushEffectsToHead: boolean
184186
initialStylesheets?: string[]
185187
}
186188
): Promise<ReadableStream<Uint8Array>> {
@@ -193,15 +195,22 @@ export async function continueFromInitialStream(
193195

194196
const transforms: Array<TransformStream<Uint8Array, Uint8Array>> = [
195197
createBufferedTransformStream(),
196-
flushEffectHandler ? createFlushEffectStream(flushEffectHandler) : null,
198+
flushEffectHandler && !flushEffectsToHead
199+
? createFlushEffectStream(flushEffectHandler)
200+
: null,
197201
suffixUnclosed != null ? createDeferredSuffixStream(suffixUnclosed) : null,
198202
dataStream ? createInlineDataStream(dataStream) : null,
199203
suffixUnclosed != null ? createSuffixStream(closeTag) : null,
200-
createHeadInjectionTransformStream(
201-
(initialStylesheets || [])
204+
createHeadInjectionTransformStream(() => {
205+
const inlineStyleLinks = (initialStylesheets || [])
202206
.map((href) => `<link rel="stylesheet" href="/_next/${href}">`)
203207
.join('')
204-
),
208+
// TODO-APP: Inject flush effects to end of head in app layout rendering, to avoid
209+
// hydration errors. Remove this once it's ready to be handled by react itself.
210+
const flushEffectsContent =
211+
flushEffectHandler && flushEffectsToHead ? flushEffectHandler() : ''
212+
return inlineStyleLinks + flushEffectsContent
213+
}),
205214
].filter(nonNullable)
206215

207216
return transforms.reduce(

packages/next/server/render.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,7 @@ export async function renderToHTML(
12691269
dataStream: serverComponentsInlinedTransformStream?.readable,
12701270
generateStaticHTML,
12711271
flushEffectHandler,
1272+
flushEffectsToHead: false,
12721273
})
12731274
}
12741275

test/e2e/app-dir/rsc-basic.test.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ describe('app dir - react server components', () => {
4141
'next.config.js': new FileRef(path.join(appDir, 'next.config.js')),
4242
},
4343
dependencies: {
44+
'styled-jsx': 'latest',
45+
'styled-components': '6.0.0-alpha.5',
4446
react: 'experimental',
4547
'react-dom': 'experimental',
4648
},
@@ -320,11 +322,15 @@ describe('app dir - react server components', () => {
320322
expect(content).toContain('bar.server.js:')
321323
})
322324

323-
it.skip('should SSR styled-jsx correctly', async () => {
324-
const html = await renderViaHTTP(next.url, '/styled-jsx')
325-
const styledJsxClass = getNodeBySelector(html, 'h1').attr('class')
325+
it('should render initial styles of css-in-js in SSR correctly', async () => {
326+
const html = await renderViaHTTP(next.url, '/css-in-js')
327+
const head = getNodeBySelector(html, 'head').html()
326328

327-
expect(html).toContain(`h1.${styledJsxClass}{color:red}`)
329+
// from styled-jsx
330+
expect(head).toMatch(/{color:(\s*)purple;?}/)
331+
332+
// from styled-components
333+
expect(head).toMatch(/{color:(\s*)blue;?}/)
328334
})
329335

330336
it('should support streaming for flight response', async () => {

test/e2e/app-dir/rsc-basic/app/layout.server.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ export default function AppLayout({ children }) {
55
return (
66
<html>
77
<head>
8-
{/* TODO-APP: title will cause hydration issue with style injection */}
9-
{/* <title>RSC</title> */}
8+
<title>RSC</title>
109
</head>
1110
<body>
1211
<RootStyleRegistry>{children}</RootStyleRegistry>

0 commit comments

Comments
 (0)