Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow developers to disable concurrent rendering (reactRoot: false) with React 18 #36419

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin'
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
import type { webpack5 as webpack } from 'next/dist/compiled/webpack/webpack'
import { recursiveCopy } from '../lib/recursive-copy'
import { shouldUseReactRoot } from '../server/config'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand Down Expand Up @@ -160,8 +159,8 @@ export default async function build(
setGlobal('distDir', distDir)

// We enable concurrent features (Fizz-related rendering architecture) when
// using React 18 or experimental.
const hasReactRoot = shouldUseReactRoot()
// using React 18 or experimental and the developer hasn't opted out
const hasReactRoot = !!config.experimental.reactRoot
const hasConcurrentFeatures = hasReactRoot
const hasServerComponents =
hasReactRoot && !!config.experimental.serverComponents
Expand Down
2 changes: 1 addition & 1 deletion packages/next/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function assignDefaults(userConfig: { [key: string]: any }) {
}

const hasReactRoot = shouldUseReactRoot()
if (hasReactRoot) {
if (hasReactRoot && userConfig.experimental?.reactRoot !== false) {
// users might not have the `experimental` key in their config
result.experimental = result.experimental || {}
result.experimental.reactRoot = true
Expand Down
6 changes: 6 additions & 0 deletions test/integration/react-18-serial/app/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
experimental: {
// Explicitly opt out of concurrent rendering
reactRoot: false,
},
}
11 changes: 11 additions & 0 deletions test/integration/react-18-serial/app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"scripts": {
"dev": "yarn next dev",
"build": "yarn next build",
"start": "yarn next start"
},
"dependencies": {
"react": "*",
"react-dom": "*"
}
}
36 changes: 36 additions & 0 deletions test/integration/react-18-serial/app/pages/_document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Document, { Head, Html, Main, NextScript } from 'next/document'

export default class CustomDocument extends Document {
static async getInitialProps(ctx) {
let mainContentLength

const originalRenderPage = ctx.renderPage
// Keep renderPage synchronous since this integration test checks for
// serial rendering
ctx.renderPage = () => {
const result = originalRenderPage()
mainContentLength = result.html.length
return result
}

const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps, mainContentLength }
}

render() {
return (
<Html>
<Head>
<meta
name="test-main-content-length"
value={this.props.mainContentLength}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
12 changes: 12 additions & 0 deletions test/integration/react-18-serial/app/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ReactDOM from 'react-dom'

export default function Index() {
if (typeof window !== 'undefined') {
window.didHydrate = true
}
return (
<div>
<p id="react-dom-version">{ReactDOM.version}</p>
</div>
)
}
17 changes: 17 additions & 0 deletions test/integration/react-18-serial/test/basics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-env jest */

import webdriver from 'next-webdriver'
import { renderViaHTTP } from 'next-test-utils'

export default (context, env) => {
it('renders pages in serial', async () => {
const html = await renderViaHTTP(context.appPort, '/')
expect(html).toMatch(/<meta name="test-main-content-length" value="\d+"\/>/)
})

it('hydrates correctly for normal page', async () => {
const browser = await webdriver(context.appPort, '/')
expect(await browser.eval('window.didHydrate')).toBe(true)
expect(await browser.elementById('react-dom-version').text()).toMatch(/18/)
})
}
18 changes: 18 additions & 0 deletions test/integration/react-18-serial/test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-env jest */

import { join } from 'path'

import { runDevSuite, runProdSuite } from 'next-test-utils'
import basics from './basics'

const appDir = join(__dirname, '../app')

describe('Basics', () => {
runTests('react 18 with reactRoot set to false', basics)
})

function runTests(name, fn, opts) {
const suiteOptions = { ...opts, runTests: fn }
runDevSuite(name, appDir, suiteOptions)
runProdSuite(name, appDir, suiteOptions)
}