Skip to content

Commit 8890a5e

Browse files
authored
run config deprecation checks only on user-provided configuration (#82613)
The deprecation warning code is fairly brittle right now since there's a mixture of calling things with `userConfig` and `result`. It was assumed that `userConfig` was the actual user-provided configuration, but it turns out it's actually the default configuration when a user configuration isn't specified. This moves all the deprecation checks to be before we actually do any sort of merging. This restores the intent behind `userConfig` and will prevent deprecation warnings from being logged due to defaults. Alternative to #82593
1 parent 8ad2eff commit 8890a5e

File tree

7 files changed

+168
-77
lines changed

7 files changed

+168
-77
lines changed

packages/next/src/server/config.ts

Lines changed: 91 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,84 @@ export function warnOptionHasBeenDeprecated(
8989
return hasWarned
9090
}
9191

92+
function checkDeprecations(
93+
userConfig: NextConfig,
94+
configFileName: string,
95+
silent: boolean,
96+
dir: string
97+
) {
98+
warnOptionHasBeenDeprecated(
99+
userConfig,
100+
'amp',
101+
`Built-in amp support is deprecated and the \`amp\` configuration option will be removed in Next.js 16.`,
102+
silent
103+
)
104+
105+
warnOptionHasBeenDeprecated(
106+
userConfig,
107+
'experimental.amp',
108+
`Built-in amp support is deprecated and the \`experimental.amp\` configuration option will be removed in Next.js 16.`,
109+
silent
110+
)
111+
112+
if (userConfig.experimental?.dynamicIO !== undefined) {
113+
warnOptionHasBeenDeprecated(
114+
userConfig,
115+
'experimental.dynamicIO',
116+
`\`experimental.dynamicIO\` has been renamed to \`experimental.cacheComponents\`. Please update your ${configFileName} file accordingly.`,
117+
silent
118+
)
119+
}
120+
121+
warnOptionHasBeenDeprecated(
122+
userConfig,
123+
'experimental.instrumentationHook',
124+
`\`experimental.instrumentationHook\` is no longer needed, because \`instrumentation.js\` is available by default. You can remove it from ${configFileName}.`,
125+
silent
126+
)
127+
128+
warnOptionHasBeenDeprecated(
129+
userConfig,
130+
'experimental.after',
131+
`\`experimental.after\` is no longer needed, because \`after\` is available by default. You can remove it from ${configFileName}.`,
132+
silent
133+
)
134+
135+
warnOptionHasBeenDeprecated(
136+
userConfig,
137+
'devIndicators.appIsrStatus',
138+
`\`devIndicators.appIsrStatus\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
139+
silent
140+
)
141+
142+
warnOptionHasBeenDeprecated(
143+
userConfig,
144+
'devIndicators.buildActivity',
145+
`\`devIndicators.buildActivity\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
146+
silent
147+
)
148+
149+
warnOptionHasBeenDeprecated(
150+
userConfig,
151+
'devIndicators.buildActivityPosition',
152+
`\`devIndicators.buildActivityPosition\` has been renamed to \`devIndicators.position\`. Please update your ${configFileName} file accordingly.`,
153+
silent
154+
)
155+
156+
// i18n deprecation for App Router
157+
if (userConfig.i18n) {
158+
const hasAppDir = Boolean(findDir(dir, 'app'))
159+
if (hasAppDir) {
160+
warnOptionHasBeenDeprecated(
161+
userConfig,
162+
'i18n',
163+
`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`,
164+
silent
165+
)
166+
}
167+
}
168+
}
169+
92170
export function warnOptionHasBeenMovedOutOfExperimental(
93171
config: NextConfig,
94172
oldExperimentalKey: string,
@@ -161,35 +239,8 @@ function assignDefaults(
161239
delete userConfig.exportTrailingSlash
162240
}
163241

164-
// There are a good amount of test fixtures that have amp enabled
165-
// that also assert on stderr output being empty, so we're gating the
166-
// warning to be skipped when running in Next.js tests until we fully
167-
// remove the feature.
168-
if (!process.env.__NEXT_TEST_MODE) {
169-
warnOptionHasBeenDeprecated(
170-
userConfig,
171-
'amp',
172-
`Built-in amp support is deprecated and the \`amp\` configuration option will be removed in Next.js 16.`,
173-
silent
174-
)
175-
176-
warnOptionHasBeenDeprecated(
177-
userConfig,
178-
'experimental.amp',
179-
`Built-in amp support is deprecated and the \`experimental.amp\` configuration option will be removed in Next.js 16.`,
180-
silent
181-
)
182-
}
183-
184-
// Handle deprecation of experimental.dynamicIO and migrate to experimental.cacheComponents
242+
// Handle migration of experimental.dynamicIO to experimental.cacheComponents
185243
if (userConfig.experimental?.dynamicIO !== undefined) {
186-
warnOptionHasBeenDeprecated(
187-
userConfig,
188-
'experimental.dynamicIO',
189-
`\`experimental.dynamicIO\` has been renamed to \`experimental.cacheComponents\`. Please update your ${configFileName} file accordingly.`,
190-
silent
191-
)
192-
193244
// If cacheComponents was not explicitly set by the user (i.e., it's still the default value),
194245
// use the dynamicIO value. We check against the user config, not the merged result.
195246
if (userConfig.experimental?.cacheComponents === undefined) {
@@ -532,49 +583,17 @@ function assignDefaults(
532583
silent
533584
)
534585

535-
warnOptionHasBeenDeprecated(
536-
result,
537-
'experimental.instrumentationHook',
538-
`\`experimental.instrumentationHook\` is no longer needed, because \`instrumentation.js\` is available by default. You can remove it from ${configFileName}.`,
539-
silent
540-
)
541-
542-
warnOptionHasBeenDeprecated(
543-
result,
544-
'experimental.after',
545-
`\`experimental.after\` is no longer needed, because \`after\` is available by default. You can remove it from ${configFileName}.`,
546-
silent
547-
)
548-
549-
warnOptionHasBeenDeprecated(
550-
result,
551-
'devIndicators.appIsrStatus',
552-
`\`devIndicators.appIsrStatus\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
553-
silent
554-
)
555-
556-
warnOptionHasBeenDeprecated(
557-
result,
558-
'devIndicators.buildActivity',
559-
`\`devIndicators.buildActivity\` is deprecated and no longer configurable. Please remove it from ${configFileName}.`,
560-
silent
561-
)
562-
563-
const hasWarnedBuildActivityPosition = warnOptionHasBeenDeprecated(
564-
result,
565-
'devIndicators.buildActivityPosition',
566-
`\`devIndicators.buildActivityPosition\` has been renamed to \`devIndicators.position\`. Please update your ${configFileName} file accordingly.`,
567-
silent
568-
)
586+
// Handle buildActivityPosition migration (needs to be done after merging with defaults)
569587
if (
570-
hasWarnedBuildActivityPosition &&
571588
result.devIndicators !== false &&
572589
'buildActivityPosition' in result.devIndicators &&
573590
result.devIndicators.buildActivityPosition !== result.devIndicators.position
574591
) {
575-
Log.warnOnce(
576-
`The \`devIndicators\` option \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") conflicts with \`position\` ("${result.devIndicators.position}"). Using \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") for backward compatibility.`
577-
)
592+
if (!silent) {
593+
Log.warnOnce(
594+
`The \`devIndicators\` option \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") conflicts with \`position\` ("${result.devIndicators.position}"). Using \`buildActivityPosition\` ("${result.devIndicators.buildActivityPosition}") for backward compatibility.`
595+
)
596+
}
578597
result.devIndicators.position = result.devIndicators.buildActivityPosition
579598
}
580599

@@ -743,17 +762,6 @@ function assignDefaults(
743762
setHttpClientAndAgentOptions(result || defaultConfig)
744763

745764
if (result.i18n) {
746-
const hasAppDir = Boolean(findDir(dir, 'app'))
747-
748-
if (hasAppDir) {
749-
warnOptionHasBeenDeprecated(
750-
result,
751-
'i18n',
752-
`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`,
753-
silent
754-
)
755-
}
756-
757765
const { i18n } = result
758766
const i18nType = typeof i18n
759767

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

12921300
if (customConfig) {
1301+
// Check deprecation warnings on the custom config before merging with defaults
1302+
checkDeprecations(customConfig as NextConfig, configFileName, silent, dir)
1303+
12931304
const config = await applyModifyConfig(
12941305
assignDefaults(
12951306
dir,
@@ -1401,6 +1412,9 @@ export default async function loadConfig(
14011412
// Clone a new userConfig each time to avoid mutating the original
14021413
const userConfig = cloneObject(loadedConfig) as NextConfig
14031414

1415+
// Check deprecation warnings on the actual user config before merging with defaults
1416+
checkDeprecations(userConfig, configFileName, silent, dir)
1417+
14041418
// Always validate the config against schema in non minimal mode.
14051419
// Only validate once in the root Next.js process, not in forked processes.
14061420
const isRootProcess = typeof process.send !== 'function'
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
import path from 'path'
3+
4+
describe('deprecation-warnings', () => {
5+
describe('without next.config.js', () => {
6+
const { next } = nextTestSetup({
7+
files: path.join(__dirname, 'fixtures/no-config'),
8+
skipStart: true,
9+
})
10+
11+
it('should not emit any deprecation warnings when no config file exists', async () => {
12+
await next.start()
13+
14+
const logs = next.cliOutput
15+
expect(logs).not.toContain('deprecated')
16+
expect(logs).not.toContain('has been renamed')
17+
expect(logs).not.toContain('no longer needed')
18+
})
19+
})
20+
21+
describe('with deprecated config options', () => {
22+
const { next } = nextTestSetup({
23+
files: path.join(__dirname, 'fixtures/with-deprecated-config'),
24+
skipStart: true,
25+
})
26+
27+
it('should emit deprecation warnings for explicitly configured deprecated options', async () => {
28+
await next.start()
29+
30+
const logs = next.cliOutput
31+
32+
// Should warn about amp configuration
33+
expect(logs).toContain('Built-in amp support is deprecated')
34+
35+
// Should warn about experimental.instrumentationHook
36+
expect(logs).toContain('experimental.instrumentationHook')
37+
expect(logs).toContain('no longer needed')
38+
})
39+
})
40+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function RootLayout({
2+
children,
3+
}: {
4+
children: React.ReactNode
5+
}) {
6+
return (
7+
<html lang="en">
8+
<body>{children}</body>
9+
</html>
10+
)
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>Hello World</div>
3+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function RootLayout({
2+
children,
3+
}: {
4+
children: React.ReactNode
5+
}) {
6+
return (
7+
<html lang="en">
8+
<body>{children}</body>
9+
</html>
10+
)
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>Hello World</div>
3+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = {
2+
// Explicitly configure deprecated options
3+
amp: {
4+
canonicalBase: 'https://example.com',
5+
},
6+
experimental: {
7+
instrumentationHook: true,
8+
},
9+
}

0 commit comments

Comments
 (0)