diff --git a/packages/next/src/build/output/index.ts b/packages/next/src/build/output/index.ts index ef773a18a1..a304daa454 100644 --- a/packages/next/src/build/output/index.ts +++ b/packages/next/src/build/output/index.ts @@ -40,6 +40,7 @@ type BuildStatusStore = { server: WebpackStatus edgeServer: WebpackStatus trigger: string | undefined + url: string | undefined amp: AmpPageStatus } @@ -111,7 +112,7 @@ let serverWasLoading = true let edgeServerWasLoading = false buildStore.subscribe((state) => { - const { amp, client, server, edgeServer, trigger } = state + const { amp, client, server, edgeServer, trigger, url } = state const { appUrl } = consoleStore.getState() @@ -123,6 +124,7 @@ buildStore.subscribe((state) => { // If it takes more than 3 seconds to compile, mark it as loading status loading: true, trigger, + url, } as OutputState, true ) @@ -230,6 +232,7 @@ export function watchCompilers( server: { loading: true }, edgeServer: { loading: true }, trigger: 'initial', + url: undefined, }) function tapCompiler( @@ -273,6 +276,7 @@ export function watchCompilers( buildStore.setState({ client: status, trigger: undefined, + url: undefined, }) } else { buildStore.setState({ @@ -290,6 +294,7 @@ export function watchCompilers( buildStore.setState({ server: status, trigger: undefined, + url: undefined, }) } else { buildStore.setState({ @@ -307,6 +312,7 @@ export function watchCompilers( buildStore.setState({ edgeServer: status, trigger: undefined, + url: undefined, }) } else { buildStore.setState({ @@ -317,7 +323,7 @@ export function watchCompilers( } const internalSegments = ['[[...__metadata_id__]]', '[__metadata_id__]'] -export function reportTrigger(trigger: string) { +export function reportTrigger(trigger: string, url?: string) { for (const segment of internalSegments) { if (trigger.includes(segment)) { trigger = trigger.replace(segment, '') @@ -330,5 +336,6 @@ export function reportTrigger(trigger: string) { buildStore.setState({ trigger, + url, }) } diff --git a/packages/next/src/build/output/store.ts b/packages/next/src/build/output/store.ts index 0599ccf715..e15d566c5e 100644 --- a/packages/next/src/build/output/store.ts +++ b/packages/next/src/build/output/store.ts @@ -16,6 +16,7 @@ export type OutputState = | { loading: true trigger: string | undefined + url: string | undefined } | { loading: false @@ -51,6 +52,7 @@ function hasStoreChanged(nextStore: OutputState) { let startTime = 0 let trigger = '' // default, use empty string for trigger +let triggerUrl: string | undefined = undefined let loadingLogTimer: NodeJS.Timeout | null = null let traceSpan: Span | null = null @@ -66,6 +68,7 @@ store.subscribe((state) => { if (state.loading) { if (state.trigger) { trigger = state.trigger + triggerUrl = state.url if (trigger !== 'initial') { traceSpan = trace('compile-path', undefined, { trigger: trigger, @@ -73,7 +76,15 @@ store.subscribe((state) => { if (!loadingLogTimer) { // Only log compiling if compiled is not finished in 3 seconds loadingLogTimer = setTimeout(() => { - Log.wait(`Compiling ${trigger} ...`) + if ( + triggerUrl && + triggerUrl !== trigger && + process.env.NEXT_TRIGGER_URL + ) { + Log.wait(`Compiling ${trigger} (${triggerUrl}) ...`) + } else { + Log.wait(`Compiling ${trigger} ...`) + } }, MAX_LOG_SKIP_DURATION) } } diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 093fc3c8ef..4bd8258598 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -343,6 +343,7 @@ export default abstract class Server { sriEnabled?: boolean appPaths?: ReadonlyArray | null shouldEnsure?: boolean + url?: string }): Promise protected abstract getFontManifest(): FontManifest | undefined protected abstract getPrerenderManifest(): PrerenderManifest @@ -3006,7 +3007,9 @@ export default abstract class Server { } protected abstract getMiddleware(): MiddlewareRoutingItem | undefined - protected abstract getFallbackErrorComponents(): Promise + protected abstract getFallbackErrorComponents( + url?: string + ): Promise protected abstract getRoutesManifest(): NormalizedRouteManifest | undefined private async renderToResponseImpl( @@ -3249,6 +3252,7 @@ export default abstract class Server { params: {}, isAppPath: true, shouldEnsure: true, + url: ctx.req.url, }) using404Page = result !== null } @@ -3261,6 +3265,7 @@ export default abstract class Server { isAppPath: false, // Ensuring can't be done here because you never "match" a 404 route. shouldEnsure: true, + url: ctx.req.url, }) using404Page = result !== null } @@ -3283,6 +3288,7 @@ export default abstract class Server { // Ensuring can't be done here because you never "match" a 500 // route. shouldEnsure: true, + url: ctx.req.url, }) } } @@ -3296,6 +3302,7 @@ export default abstract class Server { // Ensuring can't be done here because you never "match" an error // route. shouldEnsure: true, + url: ctx.req.url, }) statusPage = '/_error' } @@ -3376,7 +3383,9 @@ export default abstract class Server { this.logError(renderToHtmlError) } res.statusCode = 500 - const fallbackComponents = await this.getFallbackErrorComponents() + const fallbackComponents = await this.getFallbackErrorComponents( + ctx.req.url + ) if (fallbackComponents) { // There was an error, so use it's definition from the route module diff --git a/packages/next/src/server/dev/hot-reloader-types.ts b/packages/next/src/server/dev/hot-reloader-types.ts index 44262520ee..0e6a49c660 100644 --- a/packages/next/src/server/dev/hot-reloader-types.ts +++ b/packages/next/src/server/dev/hot-reloader-types.ts @@ -148,11 +148,13 @@ export interface NextJsHotReloaderInterface { appPaths, definition, isApp, + url, }: { page: string clientOnly: boolean appPaths?: ReadonlyArray | null isApp?: boolean definition: RouteDefinition | undefined + url?: string }): Promise } diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index e7348cbcf4..63da5b34f7 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -313,7 +313,7 @@ export default class HotReloader implements NextJsHotReloaderInterface { if (page === '/_error' || BLOCKED_PAGES.indexOf(page) === -1) { try { - await this.ensurePage({ page, clientOnly: true }) + await this.ensurePage({ page, clientOnly: true, url: req.url }) } catch (error) { return await renderScriptError(pageBundleRes, getProperError(error)) } @@ -1471,12 +1471,14 @@ export default class HotReloader implements NextJsHotReloaderInterface { appPaths, definition, isApp, + url, }: { page: string clientOnly: boolean appPaths?: ReadonlyArray | null isApp?: boolean definition?: RouteDefinition + url?: string }): Promise { // Make sure we don't re-build or dispose prebuilt pages if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) { @@ -1494,6 +1496,7 @@ export default class HotReloader implements NextJsHotReloaderInterface { appPaths, definition, isApp, + url, }) } } diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 05d4283602..23aac6c890 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -198,11 +198,12 @@ export default class DevServer extends Server { const { pagesDir, appDir } = findPagesDir(this.dir) const ensurer: RouteEnsurer = { - ensure: async (match) => { + ensure: async (match, pathname) => { await this.ensurePage({ definition: match.definition, page: match.definition.page, clientOnly: false, + url: pathname, }) }, } @@ -559,11 +560,12 @@ export default class DevServer extends Server { return this.hasPage(this.actualMiddlewareFile!) } - protected async ensureMiddleware() { + protected async ensureMiddleware(url: string) { return this.ensurePage({ page: this.actualMiddlewareFile!, clientOnly: false, definition: undefined, + url, }) } @@ -597,15 +599,18 @@ export default class DevServer extends Server { protected async ensureEdgeFunction({ page, appPaths, + url, }: { page: string appPaths: string[] | null + url: string }) { return this.ensurePage({ page, appPaths, clientOnly: false, definition: undefined, + url, }) } @@ -762,6 +767,7 @@ export default class DevServer extends Server { clientOnly: boolean appPaths?: ReadonlyArray | null definition: RouteDefinition | undefined + url?: string }): Promise { await this.bundlerService.ensurePage(opts) } @@ -773,6 +779,7 @@ export default class DevServer extends Server { isAppPath, appPaths = null, shouldEnsure, + url, }: { page: string query: NextParsedUrlQuery @@ -781,6 +788,7 @@ export default class DevServer extends Server { sriEnabled?: boolean appPaths?: ReadonlyArray | null shouldEnsure: boolean + url?: string }): Promise { await this.ready?.promise @@ -796,6 +804,7 @@ export default class DevServer extends Server { appPaths, clientOnly: false, definition: undefined, + url, }) } @@ -812,6 +821,7 @@ export default class DevServer extends Server { params, isAppPath, shouldEnsure, + url, }) } catch (err) { if ((err as any).code !== 'ENOENT') { @@ -821,8 +831,10 @@ export default class DevServer extends Server { } } - protected async getFallbackErrorComponents(): Promise { - await this.bundlerService.getFallbackErrorComponents() + protected async getFallbackErrorComponents( + url?: string + ): Promise { + await this.bundlerService.getFallbackErrorComponents(url) return await loadDefaultErrorComponents(this.distDir) } diff --git a/packages/next/src/server/dev/on-demand-entry-handler.ts b/packages/next/src/server/dev/on-demand-entry-handler.ts index a7213abb24..4c3c6738ae 100644 --- a/packages/next/src/server/dev/on-demand-entry-handler.ts +++ b/packages/next/src/server/dev/on-demand-entry-handler.ts @@ -678,11 +678,13 @@ export function onDemandEntryHandler({ appPaths, definition, isApp, + url, }: { page: string appPaths: ReadonlyArray | null definition: RouteDefinition | undefined isApp: boolean | undefined + url?: string }): Promise { const stalledTime = 60 const stalledEnsureTimeout = setTimeout(() => { @@ -834,7 +836,7 @@ export function onDemandEntryHandler({ if (hasNewEntry) { const routePage = isApp ? route.page : normalizeAppPath(route.page) - reportTrigger(routePage) + reportTrigger(routePage, url) } if (entriesThatShouldBeInvalidated.length > 0) { @@ -877,6 +879,7 @@ export function onDemandEntryHandler({ appPaths?: ReadonlyArray | null definition?: RouteDefinition isApp?: boolean + url?: string } // Make sure that we won't have multiple invalidations ongoing concurrently. @@ -900,6 +903,7 @@ export function onDemandEntryHandler({ appPaths = null, definition, isApp, + url, }: EnsurePageOptions) { // If the route is actually an app page route, then we should have access // to the app route definition, and therefore, the appPaths from it. @@ -916,6 +920,7 @@ export function onDemandEntryHandler({ appPaths, definition, isApp, + url, }) }) }, diff --git a/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts b/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts index 78d69b95a6..6b73a5da18 100644 --- a/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts +++ b/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts @@ -9,7 +9,7 @@ import { cyan } from '../../../lib/picocolors' import type { RouteMatcher } from '../route-matchers/route-matcher' export interface RouteEnsurer { - ensure(match: RouteMatch): Promise + ensure(match: RouteMatch, pathname: string): Promise } export class DevRouteMatcherManager extends DefaultRouteMatcherManager { @@ -74,7 +74,7 @@ export class DevRouteMatcherManager extends DefaultRouteMatcherManager { for await (const development of super.matchAll(pathname, options)) { // We're here, which means that we haven't seen this match yet, so we // should try to ensure it and recompile the production matcher. - await this.ensurer.ensure(development) + await this.ensurer.ensure(development, pathname) await this.production.reload() // Iterate over the production matches again, this time we should be able diff --git a/packages/next/src/server/lib/dev-bundler-service.ts b/packages/next/src/server/lib/dev-bundler-service.ts index 99ca47415b..f847b220e1 100644 --- a/packages/next/src/server/lib/dev-bundler-service.ts +++ b/packages/next/src/server/lib/dev-bundler-service.ts @@ -26,7 +26,7 @@ export class DevBundlerService { return await this.bundler.logErrorWithOriginalStack(...args) } - public async getFallbackErrorComponents() { + public async getFallbackErrorComponents(url?: string) { await this.bundler.hotReloader.buildFallbackError() // Build the error page to ensure the fallback is built too. // TODO: See if this can be moved into hotReloader or removed. @@ -34,6 +34,7 @@ export class DevBundlerService { page: '/_error', clientOnly: false, definition: undefined, + url, }) } diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 3780c90321..98276d71a6 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -49,7 +49,7 @@ export function getResolveRoutes( opts: Parameters[0], renderServer: RenderServer, renderServerOpts: Parameters[0], - ensureMiddleware?: () => Promise + ensureMiddleware?: (url?: string) => Promise ) { type Route = { /** @@ -446,7 +446,7 @@ export function getResolveRoutes( // @ts-expect-error BaseNextRequest stuff match?.(parsedUrl.pathname, req, parsedUrl.query) && (!ensureMiddleware || - (await ensureMiddleware?.() + (await ensureMiddleware?.(req.url) .then(() => true) .catch(() => false))) ) { diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 783632028b..af3a417c2a 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -452,7 +452,11 @@ async function startWatcher(opts: SetupOpts) { const buildingIds = new Set() const readyIds = new Set() - function startBuilding(id: string, forceRebuild: boolean = false) { + function startBuilding( + id: string, + requestUrl: string | undefined, + forceRebuild: boolean = false + ) { if (!forceRebuild && readyIds.has(id)) { return () => {} } @@ -461,6 +465,7 @@ async function startWatcher(opts: SetupOpts) { { loading: true, trigger: id, + url: requestUrl, } as OutputState, true ) @@ -1144,7 +1149,11 @@ async function startWatcher(opts: SetupOpts) { false, middleware.endpoint, async () => { - const finishBuilding = startBuilding('middleware', true) + const finishBuilding = startBuilding( + 'middleware', + undefined, + true + ) await processMiddleware() await propagateServerField( 'actualMiddlewareFile', @@ -1248,6 +1257,7 @@ async function startWatcher(opts: SetupOpts) { page: denormalizedPagePath, clientOnly: false, definition: undefined, + url: req.url, }) .catch(console.error) } @@ -1355,11 +1365,12 @@ async function startWatcher(opts: SetupOpts) { // appPaths, definition, isApp, + url: requestUrl, }) { let page = definition?.pathname ?? inputPage if (page === '/_error') { - let finishBuilding = startBuilding(page) + let finishBuilding = startBuilding(page, requestUrl) try { if (globalEntries.app) { const writtenEndpoint = await processResult( @@ -1458,7 +1469,7 @@ async function startWatcher(opts: SetupOpts) { ) } - finishBuilding = startBuilding(buildingKey) + finishBuilding = startBuilding(buildingKey, requestUrl) try { if (globalEntries.app) { const writtenEndpoint = await processResult( @@ -1546,7 +1557,7 @@ async function startWatcher(opts: SetupOpts) { // since this can happen when app pages make // api requests to page API routes. - finishBuilding = startBuilding(buildingKey) + finishBuilding = startBuilding(buildingKey, requestUrl) const writtenEndpoint = await processResult( page, await route.endpoint.writeToDisk() @@ -1571,7 +1582,7 @@ async function startWatcher(opts: SetupOpts) { break } case 'app-page': { - finishBuilding = startBuilding(buildingKey) + finishBuilding = startBuilding(buildingKey, requestUrl) const writtenEndpoint = await processResult( page, await route.htmlEndpoint.writeToDisk() @@ -1622,7 +1633,7 @@ async function startWatcher(opts: SetupOpts) { break } case 'app-route': { - finishBuilding = startBuilding(buildingKey) + finishBuilding = startBuilding(buildingKey, requestUrl) const writtenEndpoint = await processResult( page, await route.endpoint.writeToDisk() @@ -2479,12 +2490,13 @@ async function startWatcher(opts: SetupOpts) { requestHandler, logErrorWithOriginalStack, - async ensureMiddleware() { + async ensureMiddleware(requestUrl?: string) { if (!serverFields.actualMiddlewareFile) return return hotReloader.ensurePage({ page: serverFields.actualMiddlewareFile, clientOnly: false, definition: undefined, + url: requestUrl, }) }, } diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 32e03d9436..7495f46aa0 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -626,6 +626,7 @@ export default class NextNodeServer extends BaseServer { query, params, isAppPath, + url, }: { page: string query: NextParsedUrlQuery @@ -636,6 +637,7 @@ export default class NextNodeServer extends BaseServer { sriEnabled?: boolean appPaths?: ReadonlyArray | null shouldEnsure: boolean + url?: string }): Promise { return getTracer().trace( NextNodeServerSpan.findPageComponents, @@ -651,6 +653,7 @@ export default class NextNodeServer extends BaseServer { query, params, isAppPath, + url, }) ) } @@ -660,11 +663,13 @@ export default class NextNodeServer extends BaseServer { query, params, isAppPath, + url: _url, }: { page: string query: NextParsedUrlQuery params: Params isAppPath: boolean + url?: string }): Promise { const pagePaths: string[] = [page] if (query.amp) { @@ -977,6 +982,7 @@ export default class NextNodeServer extends BaseServer { clientOnly: boolean appPaths?: ReadonlyArray | null match?: RouteMatch + url?: string }): Promise { throw new Error( 'Invariant: ensurePage can only be called on the development server' @@ -1288,6 +1294,7 @@ export default class NextNodeServer extends BaseServer { await this.ensurePage({ page: notFoundPathname, clientOnly: false, + url: req.url, }).catch(() => {}) } @@ -1453,10 +1460,11 @@ export default class NextNodeServer extends BaseServer { * It will make sure that the root middleware or an edge function has been compiled * so that we can run it. */ - protected async ensureMiddleware() {} + protected async ensureMiddleware(_url?: string) {} protected async ensureEdgeFunction(_params: { page: string appPaths: string[] | null + url?: string }) {} /** @@ -1523,7 +1531,7 @@ export default class NextNodeServer extends BaseServer { return { finished: false } } - await this.ensureMiddleware() + await this.ensureMiddleware(params.request.url) const middlewareInfo = this.getEdgeFunctionInfo({ page: middleware.page, middleware: true, @@ -1634,7 +1642,7 @@ export default class NextNodeServer extends BaseServer { this.stripInternalHeaders(req) try { - await this.ensureMiddleware() + await this.ensureMiddleware(req.url) result = await this.runMiddleware({ request: req, @@ -1796,7 +1804,11 @@ export default class NextNodeServer extends BaseServer { const { query, page, match } = params if (!match) - await this.ensureEdgeFunction({ page, appPaths: params.appPaths }) + await this.ensureEdgeFunction({ + page, + appPaths: params.appPaths, + url: params.req.url, + }) edgeInfo = this.getEdgeFunctionInfo({ page, middleware: false, @@ -1903,7 +1915,9 @@ export default class NextNodeServer extends BaseServer { return serverDistDir } - protected async getFallbackErrorComponents(): Promise { + protected async getFallbackErrorComponents( + _url?: string + ): Promise { // Not implemented for production use cases, this is implemented on the // development server. return null diff --git a/packages/next/src/server/web-server.ts b/packages/next/src/server/web-server.ts index 463599a306..52a3edfc1e 100644 --- a/packages/next/src/server/web-server.ts +++ b/packages/next/src/server/web-server.ts @@ -280,11 +280,13 @@ export default class NextWebServer extends BaseServer { page, query, params, + url: _url, }: { page: string query: NextParsedUrlQuery params: Params | null isAppPath: boolean + url?: string }) { const result = await this.serverOptions.webServerConfig.loadComponent(page) if (!result) return null @@ -342,7 +344,9 @@ export default class NextWebServer extends BaseServer { // The web server does not support web sockets. } - protected async getFallbackErrorComponents(): Promise { + protected async getFallbackErrorComponents( + _url?: string + ): Promise { // The web server does not need to handle fallback errors in production. return null }