Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 91 additions & 77 deletions packages/next/src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,84 @@ export function warnOptionHasBeenDeprecated(
return hasWarned
}

function checkDeprecations(
userConfig: NextConfig,
configFileName: string,
silent: boolean,
dir: string
) {
warnOptionHasBeenDeprecated(
userConfig,
'amp',
`Built-in amp support is deprecated and the \`amp\` configuration option will be removed in Next.js 16.`,
silent
)

warnOptionHasBeenDeprecated(
userConfig,
'experimental.amp',
`Built-in amp support is deprecated and the \`experimental.amp\` configuration option will be removed in Next.js 16.`,
silent
)

if (userConfig.experimental?.dynamicIO !== undefined) {
warnOptionHasBeenDeprecated(
userConfig,
'experimental.dynamicIO',
`\`experimental.dynamicIO\` has been renamed to \`experimental.cacheComponents\`. Please update your ${configFileName} file accordingly.`,
silent
)
}

warnOptionHasBeenDeprecated(
userConfig,
'experimental.instrumentationHook',
`\`experimental.instrumentationHook\` is no longer needed, because \`instrumentation.js\` is available by default. You can remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
userConfig,
'experimental.after',
`\`experimental.after\` is no longer needed, because \`after\` is available by default. You can remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
userConfig,
'devIndicators.appIsrStatus',
`\`devIndicators.appIsrStatus\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
userConfig,
'devIndicators.buildActivity',
`\`devIndicators.buildActivity\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
userConfig,
'devIndicators.buildActivityPosition',
`\`devIndicators.buildActivityPosition\` has been renamed to \`devIndicators.position\`. Please update your ${configFileName} file accordingly.`,
silent
)

// i18n deprecation for App Router
if (userConfig.i18n) {
const hasAppDir = Boolean(findDir(dir, 'app'))
if (hasAppDir) {
warnOptionHasBeenDeprecated(
userConfig,
'i18n',
`i18n configuration in ${configFileName} is unsupported in App Router.\nLearn more about internationalization in App Router: https://nextjs.org/docs/app/building-your-application/routing/internationalization`,
silent
)
}
}
}

export function warnOptionHasBeenMovedOutOfExperimental(
config: NextConfig,
oldExperimentalKey: string,
Expand Down Expand Up @@ -161,35 +239,8 @@ function assignDefaults(
delete userConfig.exportTrailingSlash
}

// There are a good amount of test fixtures that have amp enabled
// that also assert on stderr output being empty, so we're gating the
// warning to be skipped when running in Next.js tests until we fully
// remove the feature.
if (!process.env.__NEXT_TEST_MODE) {
warnOptionHasBeenDeprecated(
userConfig,
'amp',
`Built-in amp support is deprecated and the \`amp\` configuration option will be removed in Next.js 16.`,
silent
)

warnOptionHasBeenDeprecated(
userConfig,
'experimental.amp',
`Built-in amp support is deprecated and the \`experimental.amp\` configuration option will be removed in Next.js 16.`,
silent
)
}

// Handle deprecation of experimental.dynamicIO and migrate to experimental.cacheComponents
// Handle migration of experimental.dynamicIO to experimental.cacheComponents
if (userConfig.experimental?.dynamicIO !== undefined) {
warnOptionHasBeenDeprecated(
userConfig,
'experimental.dynamicIO',
`\`experimental.dynamicIO\` has been renamed to \`experimental.cacheComponents\`. Please update your ${configFileName} file accordingly.`,
silent
)

// If cacheComponents was not explicitly set by the user (i.e., it's still the default value),
// use the dynamicIO value. We check against the user config, not the merged result.
if (userConfig.experimental?.cacheComponents === undefined) {
Expand Down Expand Up @@ -532,49 +583,17 @@ function assignDefaults(
silent
)

warnOptionHasBeenDeprecated(
result,
'experimental.instrumentationHook',
`\`experimental.instrumentationHook\` is no longer needed, because \`instrumentation.js\` is available by default. You can remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
result,
'experimental.after',
`\`experimental.after\` is no longer needed, because \`after\` is available by default. You can remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
result,
'devIndicators.appIsrStatus',
`\`devIndicators.appIsrStatus\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
silent
)

warnOptionHasBeenDeprecated(
result,
'devIndicators.buildActivity',
`\`devIndicators.buildActivity\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
silent
)

const hasWarnedBuildActivityPosition = warnOptionHasBeenDeprecated(
result,
'devIndicators.buildActivityPosition',
`\`devIndicators.buildActivityPosition\` has been renamed to \`devIndicators.position\`. Please update your ${configFileName} file accordingly.`,
silent
)
// Handle buildActivityPosition migration (needs to be done after merging with defaults)
if (
hasWarnedBuildActivityPosition &&
result.devIndicators !== false &&
'buildActivityPosition' in result.devIndicators &&
result.devIndicators.buildActivityPosition !== result.devIndicators.position
) {
Log.warnOnce(
`The \`devIndicators\` option \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") conflicts with \`position\` ("${result.devIndicators.position}"). Using \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") for backward compatibility.`
)
if (!silent) {
Log.warnOnce(
`The \`devIndicators\` option \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") conflicts with \`position\` ("${result.devIndicators.position}"). Using \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") for backward compatibility.`
)
}
result.devIndicators.position = result.devIndicators.buildActivityPosition
}

Expand Down Expand Up @@ -743,17 +762,6 @@ function assignDefaults(
setHttpClientAndAgentOptions(result || defaultConfig)

if (result.i18n) {
const hasAppDir = Boolean(findDir(dir, 'app'))

if (hasAppDir) {
warnOptionHasBeenDeprecated(
result,
'i18n',
`i18n configuration in ${configFileName} is unsupported in App Router.\nLearn more about internationalization in App Router: https://nextjs.org/docs/app/building-your-application/routing/internationalization`,
silent
)
}

const { i18n } = result
const i18nType = typeof i18n

Expand Down Expand Up @@ -1290,6 +1298,9 @@ export default async function loadConfig(
const configuredExperimentalFeatures: ConfiguredExperimentalFeature[] = []

if (customConfig) {
// Check deprecation warnings on the custom config before merging with defaults
checkDeprecations(customConfig as NextConfig, configFileName, silent, dir)

const config = await applyModifyConfig(
assignDefaults(
dir,
Expand Down Expand Up @@ -1401,6 +1412,9 @@ export default async function loadConfig(
// Clone a new userConfig each time to avoid mutating the original
const userConfig = cloneObject(loadedConfig) as NextConfig

// Check deprecation warnings on the actual user config before merging with defaults
checkDeprecations(userConfig, configFileName, silent, dir)

// Always validate the config against schema in non minimal mode.
// Only validate once in the root Next.js process, not in forked processes.
const isRootProcess = typeof process.send !== 'function'
Expand Down
40 changes: 40 additions & 0 deletions test/e2e/deprecation-warnings/deprecation-warnings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { nextTestSetup } from 'e2e-utils'
import path from 'path'

describe('deprecation-warnings', () => {
describe('without next.config.js', () => {
const { next } = nextTestSetup({
files: path.join(__dirname, 'fixtures/no-config'),
skipStart: true,
})

it('should not emit any deprecation warnings when no config file exists', async () => {
await next.start()

const logs = next.cliOutput
expect(logs).not.toContain('deprecated')
expect(logs).not.toContain('has been renamed')
expect(logs).not.toContain('no longer needed')
})
})

describe('with deprecated config options', () => {
const { next } = nextTestSetup({
files: path.join(__dirname, 'fixtures/with-deprecated-config'),
skipStart: true,
})

it('should emit deprecation warnings for explicitly configured deprecated options', async () => {
await next.start()

const logs = next.cliOutput

// Should warn about amp configuration
expect(logs).toContain('Built-in amp support is deprecated')

// Should warn about experimental.instrumentationHook
expect(logs).toContain('experimental.instrumentationHook')
expect(logs).toContain('no longer needed')
})
})
})
11 changes: 11 additions & 0 deletions test/e2e/deprecation-warnings/fixtures/no-config/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
3 changes: 3 additions & 0 deletions test/e2e/deprecation-warnings/fixtures/no-config/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <div>Hello World</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <div>Hello World</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
// Explicitly configure deprecated options
amp: {
canonicalBase: 'https://example.com',
},
experimental: {
instrumentationHook: true,
},
}
Loading