Skip to content

Commit e4c838c

Browse files
authored
Add --debug-prerender option for next build (#80667)
When running `next build --debug-prerender`, we will set a few experimental flags that ensure the following: - No minification is applied to server code. - `experimental.serverMinification = false`, or - `experimental.turbopackMinify = false` - Source maps for server bundles are generated. - `experimental.serverSourceMaps = true` - Source maps are consumed by Node.js in the spawned child processes that prerender the routes. - `experimental.enablePrerenderSourceMaps = true` - The build is not exited after the first error to show a complete list of prerender errors. - `experimental.prerenderEarlyExit = false` While `next dev` remains the primary recommended way to debug prerender errors when `dynamicIO` is enabled, this build option does offer an alternative to get a more usable build output, with readable stacks and code frames. **Note:** For performance reasons, artifacts generated with `next build --debug-prerender` should not be deployed to production. This mode is only intended for debugging purposes. Closes NAR-142
1 parent 75b7fe2 commit e4c838c

File tree

16 files changed

+383
-69
lines changed

16 files changed

+383
-69
lines changed

packages/next/src/bin/next.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ program
124124
)}`
125125
)
126126
.option('-d, --debug', 'Enables a more verbose build output.')
127-
127+
.option(
128+
'--debug-prerender',
129+
'Enables debug mode for prerendering. Not for production use!'
130+
)
128131
.option('--no-lint', 'Disables linting.')
129132
.option('--no-mangling', 'Disables mangling.')
130133
.option('--profile', 'Enables production profiling for React.')

packages/next/src/build/build-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,5 @@ export const NextBuildContext: Partial<{
9494
fetchCacheKeyPrefix?: string
9595
allowedRevalidateHeaderKeys?: string[]
9696
isCompileMode?: boolean
97+
debugPrerender: boolean
9798
}> = {}

packages/next/src/build/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ export default async function build(
807807
dir: string,
808808
reactProductionProfiling = false,
809809
debugOutput = false,
810+
debugPrerender = false,
810811
runLint = true,
811812
noMangling = false,
812813
appDirOnly = false,
@@ -832,6 +833,7 @@ export default async function build(
832833
NextBuildContext.appDirOnly = appDirOnly
833834
NextBuildContext.reactProductionProfiling = reactProductionProfiling
834835
NextBuildContext.noMangling = noMangling
836+
NextBuildContext.debugPrerender = debugPrerender
835837

836838
await nextBuildSpan.traceAsyncFn(async () => {
837839
// attempt to load global env values so they are available in next.config.js
@@ -850,6 +852,7 @@ export default async function build(
850852
// Log for next.config loading process
851853
silent: false,
852854
reactProductionProfiling,
855+
debugPrerender,
853856
}),
854857
turborepoAccessTraceResult
855858
)
@@ -970,10 +973,12 @@ export default async function build(
970973
)
971974

972975
// Always log next version first then start rest jobs
973-
const { envInfo, experimentalFeatures } = await getStartServerInfo(
976+
const { envInfo, experimentalFeatures } = await getStartServerInfo({
974977
dir,
975-
false
976-
)
978+
dev: false,
979+
debugPrerender,
980+
})
981+
977982
logStartInfo({
978983
networkUrl: null,
979984
appUrl: null,
@@ -2738,6 +2743,7 @@ export default async function build(
27382743
silent: true,
27392744
buildExport: true,
27402745
debugOutput,
2746+
debugPrerender,
27412747
pages: combinedPages,
27422748
outdir,
27432749
statusMessage: 'Generating static pages',

packages/next/src/build/turbopack-build/impl.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ export async function workerMain(workerData: {
249249
/// load the config because it's not serializable
250250
NextBuildContext.config = await loadConfig(
251251
PHASE_PRODUCTION_BUILD,
252-
NextBuildContext.dir!
252+
NextBuildContext.dir!,
253+
{ debugPrerender: NextBuildContext.debugPrerender }
253254
)
254255

255256
// Matches handling in build/index.ts

packages/next/src/build/webpack-build/impl.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,8 @@ export async function workerMain(workerData: {
385385
/// load the config because it's not serializable
386386
NextBuildContext.config = await loadConfig(
387387
PHASE_PRODUCTION_BUILD,
388-
NextBuildContext.dir!
388+
NextBuildContext.dir!,
389+
{ debugPrerender: NextBuildContext.debugPrerender }
389390
)
390391
NextBuildContext.nextBuildSpan = trace(
391392
`worker-main-${workerData.compilerName}`

packages/next/src/cli/next-build.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { disableMemoryDebuggingMode } from '../lib/memory/shutdown'
1313

1414
export type NextBuildOptions = {
1515
debug?: boolean
16+
debugPrerender?: boolean
1617
profile?: boolean
1718
lint: boolean
1819
mangling: boolean
@@ -31,6 +32,7 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {
3132

3233
const {
3334
debug,
35+
debugPrerender,
3436
experimentalDebugMemoryUsage,
3537
profile,
3638
lint,
@@ -51,7 +53,7 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {
5153

5254
if (!mangling) {
5355
warn(
54-
'Mangling is disabled. Note: This may affect performance and should only be used for debugging purposes.'
56+
`Mangling is disabled. ${italic('Note: This may affect performance and should only be used for debugging purposes.')}`
5557
)
5658
}
5759

@@ -61,6 +63,14 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {
6163
)
6264
}
6365

66+
if (debugPrerender) {
67+
warn(
68+
`Prerendering is running in debug mode. ${italic(
69+
'Note: This may affect performance and should not be used for production.'
70+
)}`
71+
)
72+
}
73+
6474
if (experimentalDebugMemoryUsage) {
6575
process.env.EXPERIMENTAL_DEBUG_MEMORY_USAGE = '1'
6676
enableMemoryDebuggingMode()
@@ -83,6 +93,7 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {
8393
dir,
8494
profile,
8595
debug || Boolean(process.env.NEXT_DEBUG_BUILD),
96+
debugPrerender,
8697
lint,
8798
!mangling,
8899
experimentalAppOnly,

packages/next/src/export/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ async function exportAppImpl(
8989

9090
const nextConfig =
9191
options.nextConfig ||
92-
(await span
93-
.traceChild('load-next-config')
94-
.traceAsyncFn(() => loadConfig(PHASE_EXPORT, dir)))
92+
(await span.traceChild('load-next-config').traceAsyncFn(() =>
93+
loadConfig(PHASE_EXPORT, dir, {
94+
debugPrerender: options.debugPrerender,
95+
})
96+
))
9597

9698
const distDir = join(dir, nextConfig.distDir)
9799
const telemetry = options.buildExport ? null : new Telemetry({ distDir })

packages/next/src/export/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export interface ExportAppOptions {
105105
enabledDirectories: NextEnabledDirectories
106106
silent?: boolean
107107
debugOutput?: boolean
108+
debugPrerender?: boolean
108109
pages?: string[]
109110
buildExport: boolean
110111
statusMessage?: string

packages/next/src/server/config.ts

Lines changed: 111 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as ciEnvironment from '../server/ci-info'
77
import {
88
CONFIG_FILES,
99
PHASE_DEVELOPMENT_SERVER,
10+
PHASE_EXPORT,
1011
PHASE_PRODUCTION_BUILD,
1112
PHASE_PRODUCTION_SERVER,
1213
} from '../shared/lib/constants'
@@ -1156,14 +1157,18 @@ export default async function loadConfig(
11561157
customConfig,
11571158
rawConfig,
11581159
silent = true,
1159-
onLoadUserConfig,
1160+
reportExperimentalFeatures,
11601161
reactProductionProfiling,
1162+
debugPrerender,
11611163
}: {
11621164
customConfig?: object | null
11631165
rawConfig?: boolean
11641166
silent?: boolean
1165-
onLoadUserConfig?: (conf: NextConfig) => void
1167+
reportExperimentalFeatures?: (
1168+
configuredExperimentalFeatures: ConfiguredExperimentalFeature[]
1169+
) => void
11661170
reactProductionProfiling?: boolean
1171+
debugPrerender?: boolean
11671172
} = {}
11681173
): Promise<NextConfigComplete> {
11691174
if (!process.env.__NEXT_PRIVATE_RENDER_WORKER) {
@@ -1259,11 +1264,35 @@ export default async function loadConfig(
12591264
throw err
12601265
}
12611266

1267+
const loadedConfig = Object.freeze(
1268+
(await normalizeConfig(
1269+
phase,
1270+
interopDefault(userConfigModule)
1271+
)) as NextConfig
1272+
)
1273+
1274+
const configuredExperimentalFeatures: ConfiguredExperimentalFeature[] = []
1275+
1276+
if (reportExperimentalFeatures && loadedConfig.experimental) {
1277+
for (const name of Object.keys(
1278+
loadedConfig.experimental
1279+
) as (keyof ExperimentalConfig)[]) {
1280+
const value = loadedConfig.experimental[name]
1281+
1282+
if (name === 'turbo' && !process.env.TURBOPACK) {
1283+
// Ignore any Turbopack config if Turbopack is not enabled
1284+
continue
1285+
}
1286+
1287+
addConfiguredExperimentalFeature(
1288+
configuredExperimentalFeatures,
1289+
name,
1290+
value
1291+
)
1292+
}
1293+
}
1294+
12621295
// Clone a new userConfig each time to avoid mutating the original
1263-
const loadedConfig = (await normalizeConfig(
1264-
phase,
1265-
interopDefault(userConfigModule)
1266-
)) as NextConfig
12671296
const userConfig = cloneObject(loadedConfig) as NextConfig
12681297

12691298
if (!process.env.NEXT_MINIMAL) {
@@ -1382,7 +1411,45 @@ export default async function loadConfig(
13821411
userConfig.htmlLimitedBots = userConfig.htmlLimitedBots.source
13831412
}
13841413

1385-
onLoadUserConfig?.(Object.freeze(loadedConfig))
1414+
if (
1415+
debugPrerender &&
1416+
(phase === PHASE_PRODUCTION_BUILD || phase === PHASE_EXPORT)
1417+
) {
1418+
userConfig.experimental ??= {}
1419+
1420+
setExperimentalFeatureForDebugPrerender(
1421+
userConfig.experimental,
1422+
'serverSourceMaps',
1423+
true,
1424+
reportExperimentalFeatures ? configuredExperimentalFeatures : undefined
1425+
)
1426+
1427+
setExperimentalFeatureForDebugPrerender(
1428+
userConfig.experimental,
1429+
process.env.TURBOPACK ? 'turbopackMinify' : 'serverMinification',
1430+
false,
1431+
reportExperimentalFeatures ? configuredExperimentalFeatures : undefined
1432+
)
1433+
1434+
setExperimentalFeatureForDebugPrerender(
1435+
userConfig.experimental,
1436+
'enablePrerenderSourceMaps',
1437+
true,
1438+
reportExperimentalFeatures ? configuredExperimentalFeatures : undefined
1439+
)
1440+
1441+
setExperimentalFeatureForDebugPrerender(
1442+
userConfig.experimental,
1443+
'prerenderEarlyExit',
1444+
false,
1445+
reportExperimentalFeatures ? configuredExperimentalFeatures : undefined
1446+
)
1447+
}
1448+
1449+
if (reportExperimentalFeatures) {
1450+
reportExperimentalFeatures(configuredExperimentalFeatures)
1451+
}
1452+
13861453
const completeConfig = assignDefaults(
13871454
dir,
13881455
{
@@ -1427,48 +1494,50 @@ export default async function loadConfig(
14271494
return await applyModifyConfig(completeConfig, phase, silent)
14281495
}
14291496

1430-
export type ConfiguredExperimentalFeature =
1431-
| { name: keyof ExperimentalConfig; type: 'boolean'; value: boolean }
1432-
| { name: keyof ExperimentalConfig; type: 'number'; value: number }
1433-
| { name: keyof ExperimentalConfig; type: 'other' }
1497+
export type ConfiguredExperimentalFeature = {
1498+
key: keyof ExperimentalConfig
1499+
value: ExperimentalConfig[keyof ExperimentalConfig]
1500+
reason?: string
1501+
}
14341502

1435-
export function getConfiguredExperimentalFeatures(
1436-
userNextConfigExperimental: NextConfig['experimental']
1503+
export function addConfiguredExperimentalFeature<
1504+
KeyType extends keyof ExperimentalConfig,
1505+
>(
1506+
configuredExperimentalFeatures: ConfiguredExperimentalFeature[],
1507+
key: KeyType,
1508+
value: ExperimentalConfig[KeyType],
1509+
reason?: string
14371510
) {
1438-
const configuredExperimentalFeatures: ConfiguredExperimentalFeature[] = []
1439-
1440-
if (!userNextConfigExperimental) {
1441-
return configuredExperimentalFeatures
1511+
if (value !== (defaultConfig.experimental as Record<string, unknown>)[key]) {
1512+
configuredExperimentalFeatures.push({ key, value, reason })
14421513
}
1514+
}
14431515

1444-
// defaultConfig.experimental is predefined and will never be undefined
1445-
// This is only a type guard for the typescript
1446-
if (defaultConfig.experimental) {
1447-
for (const name of Object.keys(
1448-
userNextConfigExperimental
1449-
) as (keyof ExperimentalConfig)[]) {
1450-
const value = userNextConfigExperimental[name]
1451-
1452-
if (name === 'turbo' && !process.env.TURBOPACK) {
1453-
// Ignore any Turbopack config if Turbopack is not enabled
1454-
continue
1455-
}
1516+
function setExperimentalFeatureForDebugPrerender<
1517+
KeyType extends keyof ExperimentalConfig,
1518+
>(
1519+
experimentalConfig: ExperimentalConfig,
1520+
key: KeyType,
1521+
value: ExperimentalConfig[KeyType],
1522+
configuredExperimentalFeatures: ConfiguredExperimentalFeature[] | undefined
1523+
) {
1524+
if (experimentalConfig[key] !== value) {
1525+
experimentalConfig[key] = value
14561526

1457-
if (
1458-
name in defaultConfig.experimental &&
1459-
value !== (defaultConfig.experimental as Record<string, unknown>)[name]
1460-
) {
1461-
configuredExperimentalFeatures.push(
1462-
typeof value === 'boolean'
1463-
? { name, type: 'boolean', value }
1464-
: typeof value === 'number'
1465-
? { name, type: 'number', value }
1466-
: { name, type: 'other' }
1467-
)
1468-
}
1527+
if (configuredExperimentalFeatures) {
1528+
const action =
1529+
value === true ? 'enabled' : value === false ? 'disabled' : 'set'
1530+
1531+
const reason = `${action} by \`--debug-prerender\``
1532+
1533+
addConfiguredExperimentalFeature(
1534+
configuredExperimentalFeatures,
1535+
key,
1536+
value,
1537+
reason
1538+
)
14691539
}
14701540
}
1471-
return configuredExperimentalFeatures
14721541
}
14731542

14741543
function cloneObject(obj: any): any {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy