diff --git a/.github/workflows/test_react_experimental.yml b/.github/workflows/test_react_experimental.yml index 892a7dfab9985..bbfb890544d67 100644 --- a/.github/workflows/test_react_experimental.yml +++ b/.github/workflows/test_react_experimental.yml @@ -29,9 +29,9 @@ jobs: # needs: build env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_PRIVATE_REACT_MODE: concurrent HEADLESS: true NEXT_PRIVATE_SKIP_SIZE_TESTS: true + NEXT_PRIVATE_REACT_ROOT: 1 strategy: fail-fast: false matrix: diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 0e9262d5572ac..35a752beb627a 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1048,8 +1048,8 @@ export default async function getBaseWebpackConfig( 'process.env.__NEXT_STRICT_MODE': JSON.stringify( config.reactStrictMode ), - 'process.env.__NEXT_REACT_MODE': JSON.stringify( - config.experimental.reactMode + 'process.env.__NEXT_REACT_ROOT': JSON.stringify( + config.experimental.reactRoot ), 'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify( config.optimizeFonts && !dev diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 10e3f91699951..5abce5188dec5 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -494,7 +494,8 @@ export function renderError(renderErrorProps: RenderErrorProps): Promise { } let reactRoot: any = null -let shouldUseHydrate: boolean = typeof ReactDOM.hydrate === 'function' +let shouldHydrate: boolean = typeof ReactDOM.hydrate === 'function' + function renderReactElement( domEl: HTMLElement, fn: (cb: () => void) => JSX.Element @@ -504,24 +505,24 @@ function renderReactElement( performance.mark('beforeRender') } - const reactEl = fn( - shouldUseHydrate ? markHydrateComplete : markRenderComplete - ) - if (process.env.__NEXT_REACT_MODE !== 'legacy') { + const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete) + if (process.env.__NEXT_REACT_ROOT) { if (!reactRoot) { - const opts = { hydrate: shouldUseHydrate } - reactRoot = - process.env.__NEXT_REACT_MODE === 'concurrent' - ? (ReactDOM as any).unstable_createRoot(domEl, opts) - : (ReactDOM as any).unstable_createBlockingRoot(domEl, opts) + const createRootName = + typeof (ReactDOM as any).unstable_createRoot === 'function' + ? 'unstable_createRoot' + : 'createRoot' + reactRoot = (ReactDOM as any)[createRootName](domEl, { + hydrate: shouldHydrate, + }) } reactRoot.render(reactEl) - shouldUseHydrate = false + shouldHydrate = false } else { // The check for `.hydrate` is there to support React alternatives like preact - if (shouldUseHydrate) { + if (shouldHydrate) { ReactDOM.hydrate(reactEl, domEl) - shouldUseHydrate = false + shouldHydrate = false } else { ReactDOM.render(reactEl, domEl) } diff --git a/packages/next/next-server/server/config-shared.ts b/packages/next/next-server/server/config-shared.ts index 1e7d88065294b..b62ccca10c727 100644 --- a/packages/next/next-server/server/config-shared.ts +++ b/packages/next/next-server/server/config-shared.ts @@ -60,6 +60,7 @@ export type NextConfig = { [key: string]: any } & { skipValidation?: boolean } turboMode: boolean + reactRoot: boolean } } @@ -104,7 +105,6 @@ export const defaultConfig: NextConfig = { plugins: false, profiling: false, sprFlushToDisk: true, - reactMode: (process.env.NEXT_PRIVATE_REACT_MODE as any) || 'legacy', workerThreads: false, pageEnv: false, optimizeImages: false, @@ -115,6 +115,7 @@ export const defaultConfig: NextConfig = { externalDir: false, serialWebpackBuild: false, turboMode: false, + reactRoot: Number(process.env.NEXT_PRIVATE_REACT_ROOT) > 0, }, future: { strictPostcssConfiguration: false, diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index b8fa8597ad803..04b5691d5b0ab 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -13,7 +13,6 @@ import { loadEnvConfig } from '@next/env' export { DomainLocales, NextConfig, normalizeConfig } from './config-shared' const targets = ['server', 'serverless', 'experimental-serverless-trace'] -const reactModes = ['legacy', 'blocking', 'concurrent'] const experimentalWarning = execOnce(() => { Log.warn(chalk.bold('You have enabled experimental feature(s).')) @@ -36,6 +35,19 @@ 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 next.config.js.' + ) + 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] @@ -435,17 +447,6 @@ export default async function loadConfig( : canonicalBase) || '' } - if ( - userConfig.experimental?.reactMode && - !reactModes.includes(userConfig.experimental.reactMode) - ) { - throw new Error( - `Specified React Mode is invalid. Provided: ${ - userConfig.experimental.reactMode - } should be one of ${reactModes.join(', ')}` - ) - } - if (hasNextSupport) { userConfig.target = process.env.NEXT_PRIVATE_TARGET || 'server' }