Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/test_react_experimental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 14 additions & 13 deletions packages/next/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,8 @@ export function renderError(renderErrorProps: RenderErrorProps): Promise<any> {
}

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
Expand All @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion packages/next/next-server/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type NextConfig = { [key: string]: any } & {
skipValidation?: boolean
}
turboMode: boolean
reactRoot: boolean
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
25 changes: 13 additions & 12 deletions packages/next/next-server/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).'))
Expand All @@ -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]
Expand Down Expand Up @@ -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'
}
Expand Down