diff --git a/packages/next/bin/next.ts b/packages/next/bin/next.ts index fccca27b57f4e..dd467cb224a51 100755 --- a/packages/next/bin/next.ts +++ b/packages/next/bin/next.ts @@ -1,8 +1,8 @@ #!/usr/bin/env node import * as log from '../build/output/log' import arg from 'next/dist/compiled/arg/index.js' -import React from 'react' import { NON_STANDARD_NODE_ENV } from '../lib/constants' +import { shouldUseReactRoot } from '../server/utils' ;['react', 'react-dom'].forEach((dependency) => { try { // When 'npm link' is used it checks the clone location. Not the project. @@ -107,7 +107,6 @@ if (process.env.NODE_ENV) { ;(process.env as any).NODE_ENV = process.env.NODE_ENV || defaultEnv ;(process.env as any).NEXT_RUNTIME = 'nodejs' -const shouldUseReactRoot = parseInt(React.version) >= 18 if (shouldUseReactRoot) { ;(process.env as any).__NEXT_REACT_ROOT = 'true' } diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index e597495b6a1ea..5abc35f4487bb 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -247,7 +247,6 @@ export function getServerlessEntry(opts: { page: opts.page, poweredByHeader: opts.config.poweredByHeader ? 'true' : '', previewProps: JSON.stringify(opts.previewMode), - reactRoot: !!opts.config.experimental.reactRoot ? 'true' : '', runtimeConfig: Object.keys(opts.config.publicRuntimeConfig).length > 0 || Object.keys(opts.config.serverRuntimeConfig).length > 0 diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 97fee15486dfd..0b97e45af895d 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -364,26 +364,17 @@ export default async function getBaseWebpackConfig( rewrites.afterFiles.length > 0 || rewrites.fallback.length > 0 - // Make sure `reactRoot` is enabled when React 18 or experimental is detected. - if (hasReactRoot) { - config.experimental.reactRoot = true - } - - // Only inform during one of the builds - if (isClient && config.experimental.reactRoot && !hasReactRoot) { - // It's fine to only mention React 18 here as we don't recommend people to try experimental. - Log.warn('You have to use React 18 to use `experimental.reactRoot`.') - } - - if (isClient && config.experimental.runtime && !hasReactRoot) { - throw new Error( - '`experimental.runtime` requires `experimental.reactRoot` to be enabled along with React 18.' - ) - } - if (config.experimental.serverComponents && !hasReactRoot) { - throw new Error( - '`experimental.serverComponents` requires React 18 to be installed.' - ) + if (isClient && !hasReactRoot) { + if (config.experimental.runtime) { + throw new Error( + '`experimental.runtime` requires React 18 to be installed.' + ) + } + if (config.experimental.serverComponents) { + throw new Error( + '`experimental.serverComponents` requires React 18 to be installed.' + ) + } } const hasConcurrentFeatures = hasReactRoot @@ -1830,7 +1821,6 @@ export default async function getBaseWebpackConfig( reactProductionProfiling, webpack: !!config.webpack, hasRewrites, - reactRoot: config.experimental.reactRoot, runtime: config.experimental.runtime, swcMinify: config.swcMinify, swcLoader: useSWCLoader, diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts index cab9ec47e9529..fdea86c4fbcaa 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts @@ -55,7 +55,6 @@ export function getRender({ page, extendRenderOpts: { buildId, - reactRoot: true, runtime: 'edge', supportsDynamicHTML: true, disableOptimizedLoading: true, diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/index.ts b/packages/next/build/webpack/loaders/next-serverless-loader/index.ts index 533f4d64aa7a2..879260d278c15 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader/index.ts @@ -31,7 +31,6 @@ export type ServerlessLoaderQuery = { previewProps: string loadedEnvFiles: string i18n: string - reactRoot: string } const nextServerlessLoader: webpack.loader.Loader = function () { @@ -53,7 +52,6 @@ const nextServerlessLoader: webpack.loader.Loader = function () { previewProps, loadedEnvFiles, i18n, - reactRoot, }: ServerlessLoaderQuery = typeof this.query === 'string' ? parse(this.query.slice(1)) : this.query @@ -187,7 +185,6 @@ const nextServerlessLoader: webpack.loader.Loader = function () { canonicalBase: "${canonicalBase}", generateEtags: ${generateEtags || 'false'}, poweredByHeader: ${poweredByHeader || 'false'}, - reactRoot: ${reactRoot || 'false'}, runtimeConfig, buildManifest, diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index 676fd4072775a..8b28637fd0482 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -388,7 +388,6 @@ export default async function exportApp( optimizeCss: nextConfig.experimental.optimizeCss, nextScriptWorkers: nextConfig.experimental.nextScriptWorkers, optimizeFonts: nextConfig.optimizeFonts, - reactRoot: nextConfig.experimental.reactRoot || false, largePageDataBytes: nextConfig.experimental.largePageDataBytes, } diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index b020515203382..96c9d833473ef 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -32,7 +32,6 @@ export type RenderOptsPartial = { supportsDynamicHTML?: boolean runtime?: 'nodejs' | 'edge' serverComponents?: boolean - reactRoot: boolean } export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index c81c1175ed479..55380a10931d4 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -177,7 +177,6 @@ export default abstract class Server { serverComponentManifest?: any renderServerComponentData?: boolean serverComponentProps?: any - reactRoot: boolean largePageDataBytes?: number } protected serverOptions: ServerOptions @@ -328,7 +327,6 @@ export default abstract class Server { crossOrigin: this.nextConfig.crossOrigin ? this.nextConfig.crossOrigin : undefined, - reactRoot: this.nextConfig.experimental.reactRoot === true, largePageDataBytes: this.nextConfig.experimental.largePageDataBytes, } @@ -1292,7 +1290,7 @@ export default abstract class Server { typeof components.Document?.getInitialProps !== 'function' || // When concurrent features is enabled, the built-in `Document` // component also supports dynamic HTML. - (this.renderOpts.reactRoot && + (!!process.env.__NEXT_REACT_ROOT && NEXT_BUILTIN_DOCUMENT in components.Document) // Disable dynamic HTML in cases that we know it won't be generated, diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 0a5844b6d4ced..d637a34ef7d44 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -107,7 +107,6 @@ export interface ExperimentalConfig { validator?: string skipValidation?: boolean } - reactRoot?: boolean disableOptimizedLoading?: boolean gzipSize?: boolean craCompat?: boolean @@ -511,7 +510,6 @@ export const defaultConfig: NextConfig = { nextScriptWorkers: false, scrollRestoration: false, externalDir: false, - reactRoot: Number(process.env.NEXT_PRIVATE_REACT_ROOT) > 0, disableOptimizedLoading: false, gzipSize: true, swcFileReading: true, diff --git a/packages/next/server/config.ts b/packages/next/server/config.ts index aeabcd36ab334..452dcc7b9ab20 100644 --- a/packages/next/server/config.ts +++ b/packages/next/server/config.ts @@ -74,19 +74,6 @@ function assignDefaults(userConfig: { [key: string]: any }) { delete userConfig.exportTrailingSlash } - if (typeof userConfig.experimental?.reactMode !== 'undefined') { - console.warn( - chalk.yellow.bold('Warning: ') + - `The experimental "reactMode" option has been replaced with "reactRoot". Please update your ${configFileName}.` - ) - if (typeof userConfig.experimental?.reactRoot === 'undefined') { - userConfig.experimental.reactRoot = ['concurrent', 'blocking'].includes( - userConfig.experimental.reactMode - ) - } - delete userConfig.experimental.reactMode - } - const config = Object.keys(userConfig).reduce<{ [key: string]: any }>( (currentConfig, key) => { const value = userConfig[key] @@ -233,13 +220,6 @@ function assignDefaults(userConfig: { [key: string]: any }) { } } - const hasReactRoot = process.env.__NEXT_REACT_ROOT - if (hasReactRoot) { - // users might not have the `experimental` key in their config - result.experimental = result.experimental || {} - result.experimental.reactRoot = true - } - if (result?.images) { const images: ImageConfig = result.images diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index a02c358219c47..7031e4285b8ef 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -25,7 +25,6 @@ import type { import fs from 'fs' import { join, relative, resolve, sep } from 'path' import { IncomingMessage, ServerResponse } from 'http' -import React from 'react' import { addRequestMeta, getRequestMeta } from './request-meta' import { @@ -88,8 +87,8 @@ import { } from './body-streams' import { checkIsManualRevalidate } from './api-utils' import { isDynamicRoute } from '../shared/lib/router/utils' +import { shouldUseReactRoot } from './utils' -const shouldUseReactRoot = parseInt(React.version) >= 18 if (shouldUseReactRoot) { ;(process.env as any).__NEXT_REACT_ROOT = 'true' } diff --git a/packages/next/server/next.ts b/packages/next/server/next.ts index 9bfc2ef3927b3..a6f3bbc0581bf 100644 --- a/packages/next/server/next.ts +++ b/packages/next/server/next.ts @@ -3,7 +3,6 @@ import type { NodeRequestHandler } from './next-server' import type { UrlWithParsedQuery } from 'url' import './node-polyfill-fetch' -import React from 'react' import { default as Server } from './next-server' import * as log from '../build/output/log' import loadConfig from './config' @@ -13,6 +12,7 @@ import { PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' import { PHASE_PRODUCTION_SERVER } from '../shared/lib/constants' import { IncomingMessage, ServerResponse } from 'http' import { NextUrlWithParsedQuery } from './request-meta' +import { shouldUseReactRoot } from './utils' let ServerImpl: typeof Server @@ -183,7 +183,6 @@ function createServer(options: NextServerOptions): NextServer { ) } - const shouldUseReactRoot = parseInt(React.version) >= 18 if (shouldUseReactRoot) { ;(process.env as any).__NEXT_REACT_ROOT = 'true' } diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index a58297de60f24..b5684b5a7d0a4 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -83,16 +83,15 @@ import stripAnsi from 'next/dist/compiled/strip-ansi' import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import { postProcessHTML } from './post-process' import { htmlEscapeJsonString } from './htmlescape' -import { stripInternalQueries } from './utils' +import { shouldUseReactRoot, stripInternalQueries } from './utils' let tryGetPreviewData: typeof import('./api-utils/node').tryGetPreviewData let warn: typeof import('../build/output/log').warn const DOCTYPE = '' -const ReactDOMServer = - parseInt(React.version) >= 18 - ? require('react-dom/server.browser') - : require('react-dom/server') +const ReactDOMServer = shouldUseReactRoot + ? require('react-dom/server.browser') + : require('react-dom/server') if (process.env.NEXT_RUNTIME !== 'edge') { require('./node-polyfill-web-streams') @@ -244,7 +243,6 @@ export type RenderOptsPartial = { customServer?: boolean crossOrigin?: string images: ImageConfigComplete - reactRoot: boolean largePageDataBytes?: number } diff --git a/packages/next/server/utils.ts b/packages/next/server/utils.ts index 5f78790f3f0ff..65b238153b66d 100644 --- a/packages/next/server/utils.ts +++ b/packages/next/server/utils.ts @@ -1,4 +1,5 @@ import type { NextParsedUrlQuery } from './request-meta' +import React from 'react' import { BLOCKED_PAGES } from '../shared/lib/constants' export function isBlockedPage(pathname: string): boolean { @@ -42,3 +43,6 @@ export function stripInternalQueries(query: NextParsedUrlQuery) { return query } + +// When react version is >= 18 opt-in using reactRoot +export const shouldUseReactRoot = parseInt(React.version) >= 18 diff --git a/test/e2e/app-dir/app-rendering/next.config.js b/test/e2e/app-dir/app-rendering/next.config.js index 931d58c1b7d7e..b6e52ffc6acdf 100644 --- a/test/e2e/app-dir/app-rendering/next.config.js +++ b/test/e2e/app-dir/app-rendering/next.config.js @@ -2,7 +2,6 @@ module.exports = { experimental: { appDir: true, runtime: 'nodejs', - reactRoot: true, serverComponents: true, }, } diff --git a/test/e2e/app-dir/app/next.config.js b/test/e2e/app-dir/app/next.config.js index 931d58c1b7d7e..b6e52ffc6acdf 100644 --- a/test/e2e/app-dir/app/next.config.js +++ b/test/e2e/app-dir/app/next.config.js @@ -2,7 +2,6 @@ module.exports = { experimental: { appDir: true, runtime: 'nodejs', - reactRoot: true, serverComponents: true, }, } diff --git a/test/integration/react-18-invalid-config/index.test.js b/test/integration/react-18-invalid-config/index.test.js index dc2f1e2c930cc..f5598e394a17c 100644 --- a/test/integration/react-18-invalid-config/index.test.js +++ b/test/integration/react-18-invalid-config/index.test.js @@ -48,7 +48,7 @@ function writeNextConfig(config, reactVersion = 17) { } describe('Invalid react 18 webpack config', () => { - it('should enable `experimental.reactRoot` when `experimental.runtime` is enabled', async () => { + it('should install react 18 when `experimental.runtime` is enabled', async () => { writeNextConfig({ runtime: 'edge', }) @@ -56,7 +56,7 @@ describe('Invalid react 18 webpack config', () => { nextConfig.restore() expect(stderr).toContain( - '`experimental.runtime` requires `experimental.reactRoot` to be enabled along with React 18.' + '`experimental.runtime` requires React 18 to be installed.' ) }) }) @@ -68,20 +68,13 @@ describe('React 17 with React 18 config', () => { join(reactDomPackagePah, 'package.json'), JSON.stringify({ name: 'react-dom', version: '17.0.0' }) ) - writeNextConfig({ reactRoot: true }) + writeNextConfig({}) }) afterAll(async () => { await fs.remove(reactDomPackagePah) nextConfig.restore() }) - it('should warn user when not using react 18 and `experimental.reactRoot` is enabled', async () => { - const { stderr } = await nextBuild(appDir, [], { stderr: true, nodeArgs }) - expect(stderr).toContain( - 'You have to use React 18 to use `experimental.reactRoot`.' - ) - }) - it('suspense is not allowed in blocking rendering mode', async () => { const { stderr, code } = await nextBuild(appDir, [], { stderr: true, diff --git a/test/integration/react-18-invalid-config/next.config.js b/test/integration/react-18-invalid-config/next.config.js index 07523fe9fd1e0..4ba52ba2c8df6 100644 --- a/test/integration/react-18-invalid-config/next.config.js +++ b/test/integration/react-18-invalid-config/next.config.js @@ -1 +1 @@ -module.exports = { experimental: { reactRoot: true } } +module.exports = {} diff --git a/test/integration/react-18/app/next.config.js b/test/integration/react-18/app/next.config.js index a44130da4430c..89120f9a581a1 100644 --- a/test/integration/react-18/app/next.config.js +++ b/test/integration/react-18/app/next.config.js @@ -1,7 +1,6 @@ module.exports = { // reactStrictMode: true, experimental: { - reactRoot: true, // runtime: 'edge', }, images: { diff --git a/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js b/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js index 190994aa291b4..060d50f525d62 100644 --- a/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js +++ b/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js @@ -1,6 +1,5 @@ module.exports = { experimental: { - reactRoot: true, serverComponents: true, }, } diff --git a/test/unit/parse-page-runtime.test.ts b/test/unit/parse-page-runtime.test.ts index 50437004802ba..f377657f55b3a 100644 --- a/test/unit/parse-page-runtime.test.ts +++ b/test/unit/parse-page-runtime.test.ts @@ -5,7 +5,7 @@ const fixtureDir = join(__dirname, 'fixtures') function createNextConfig(runtime?: 'edge' | 'nodejs') { return { - experimental: { reactRoot: true, runtime }, + experimental: { runtime }, } }