diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 91fdb62bd3..c5af9cdc52 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1148,30 +1148,52 @@ export async function renderToHTML( styles: docProps.styles, } } else { - const bodyResult = async () => { - const content = ( - - {ctx.err && ErrorDebug ? ( - - ) : ( - getWrappedApp( - - ) - )} - - ) + let bodyResult - return concurrentFeatures - ? process.browser + if (concurrentFeatures) { + bodyResult = async () => { + // this must be called inside bodyResult so appWrappers is + // up to date when getWrappedApp is called + const content = + ctx.err && ErrorDebug ? ( + + + + ) : ( + + {getWrappedApp( + + )} + + ) + return process.browser ? await renderToWebStream(content) : await renderToNodeStream(content, generateStaticHTML) - : piperFromArray([ReactDOMServer.renderToString(content)]) + } + } else { + const content = + ctx.err && ErrorDebug ? ( + + + + ) : ( + + {getWrappedApp( + + )} + + ) + // for non-concurrent rendering we need to ensure App is rendered + // before _document so that updateHead is called/collected before + // rendering _document's head + const result = piperFromArray([ReactDOMServer.renderToString(content)]) + bodyResult = () => result } return { bodyResult, documentElement: () => (Document as any)(), - useMainContent: (fn?: (content: JSX.Element) => JSX.Element) => { + useMainContent: (fn?: (_content: JSX.Element) => JSX.Element) => { if (fn) { appWrappers.push(fn) } diff --git a/test/e2e/next-head/app/components/meta.js b/test/e2e/next-head/app/components/meta.js new file mode 100644 index 0000000000..3a121375fd --- /dev/null +++ b/test/e2e/next-head/app/components/meta.js @@ -0,0 +1,12 @@ +import Head from 'next/head' + +export function Meta(props) { + return ( + <> + + + + + + ) +} diff --git a/test/e2e/next-head/app/pages/_document.js b/test/e2e/next-head/app/pages/_document.js new file mode 100644 index 0000000000..7ee4a28275 --- /dev/null +++ b/test/e2e/next-head/app/pages/_document.js @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function MyDocument() { + return ( + + + +
+ + + + ) +} diff --git a/test/e2e/next-head/app/pages/index.js b/test/e2e/next-head/app/pages/index.js new file mode 100644 index 0000000000..c887c87386 --- /dev/null +++ b/test/e2e/next-head/app/pages/index.js @@ -0,0 +1,15 @@ +import Head from 'next/head' +import { Meta } from '../components/meta' + +export default function Page(props) { + return ( + <> + + + + + +

index page

+ + ) +} diff --git a/test/e2e/next-head/index.test.ts b/test/e2e/next-head/index.test.ts new file mode 100644 index 0000000000..479a67a63b --- /dev/null +++ b/test/e2e/next-head/index.test.ts @@ -0,0 +1,41 @@ +import { createNext, FileRef } from 'e2e-utils' +import { renderViaHTTP } from 'next-test-utils' +import cheerio from 'cheerio' +import webdriver from 'next-webdriver' +import { NextInstance } from 'test/lib/next-modes/base' +import { join } from 'path' + +describe('should set-up next', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, 'app/pages')), + components: new FileRef(join(__dirname, 'app/components')), + }, + }) + }) + afterAll(() => next.destroy()) + + it('should have correct head tags in initial document', async () => { + const html = await renderViaHTTP(next.url, '/') + const $ = cheerio.load(html) + + for (let i = 1; i < 5; i++) { + expect($(`meta[name="test-head-${i}"]`).attr()['content']).toBe('hello') + } + }) + + it('should have correct head tags after hydration', async () => { + const browser = await webdriver(next.url, '/') + + for (let i = 1; i < 5; i++) { + expect( + await browser + .elementByCss(`meta[name="test-head-${i}"]`) + .getAttribute('content') + ).toBe('hello') + } + }) +}) diff --git a/test/integration/document-functional-render-prop/lib/context.js b/test/integration/document-functional-render-prop/app/lib/context.js similarity index 100% rename from test/integration/document-functional-render-prop/lib/context.js rename to test/integration/document-functional-render-prop/app/lib/context.js diff --git a/test/integration/document-functional-render-prop/app/next.config.js b/test/integration/document-functional-render-prop/app/next.config.js new file mode 100644 index 0000000000..a866ec0085 --- /dev/null +++ b/test/integration/document-functional-render-prop/app/next.config.js @@ -0,0 +1,19 @@ +module.exports = { + experimental: { + reactRoot: true, + concurrentFeatures: true, + }, + webpack(config) { + const { alias } = config.resolve + // FIXME: resolving react/jsx-runtime https://github.com/facebook/react/issues/20235 + alias['react/jsx-dev-runtime'] = 'react/jsx-dev-runtime.js' + alias['react/jsx-runtime'] = 'react/jsx-runtime.js' + + // Use react 18 + alias['react'] = 'react-18' + alias['react-dom'] = 'react-dom-18' + alias['react-dom/server'] = 'react-dom-18/server' + + return config + }, +} diff --git a/test/integration/document-functional-render-prop/app/package.json b/test/integration/document-functional-render-prop/app/package.json new file mode 100644 index 0000000000..f9dafc993a --- /dev/null +++ b/test/integration/document-functional-render-prop/app/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "next": "node -r ../test/require-hook.js ../../../../packages/next/dist/bin/next", + "dev": "yarn next dev", + "build": "yarn next build", + "start": "yarn next start" + }, + "dependencies": { + "react": "*", + "react-dom": "*" + } +} diff --git a/test/integration/document-functional-render-prop/pages/_document.js b/test/integration/document-functional-render-prop/app/pages/_document.js similarity index 100% rename from test/integration/document-functional-render-prop/pages/_document.js rename to test/integration/document-functional-render-prop/app/pages/_document.js diff --git a/test/integration/document-functional-render-prop/pages/index.js b/test/integration/document-functional-render-prop/app/pages/index.js similarity index 100% rename from test/integration/document-functional-render-prop/pages/index.js rename to test/integration/document-functional-render-prop/app/pages/index.js diff --git a/test/integration/document-functional-render-prop/tests/index.test.js b/test/integration/document-functional-render-prop/tests/index.test.js index d7cb027d41..c843082fee 100644 --- a/test/integration/document-functional-render-prop/tests/index.test.js +++ b/test/integration/document-functional-render-prop/tests/index.test.js @@ -3,7 +3,8 @@ import { join } from 'path' import { findPort, launchApp, killApp, renderViaHTTP } from 'next-test-utils' -const appDir = join(__dirname, '..') +const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')] +const appDir = join(__dirname, '../app') let appPort let app @@ -11,7 +12,7 @@ describe('Functional Custom Document', () => { describe('development mode', () => { beforeAll(async () => { appPort = await findPort() - app = await launchApp(appDir, appPort) + app = await launchApp(appDir, appPort, { nodeArgs }) }) afterAll(() => killApp(app))