build: detect instrumentation/middleware/proxy with multi-segment pageExtensions#92934
Open
yogeshwaran-c wants to merge 1 commit intovercel:canaryfrom
Open
Conversation
…eExtensions When `next.config.js` declares a multi-segment custom `pageExtensions` entry such as `'universal.ts'` and the user names their hook `src/instrumentation.universal.ts`, the regex that filters root files correctly matches the file (because `.` in the extension list is treated as `\.|.` — an "any char" wildcard — which still happens to hit `universal.ts`), but the per-file bucketing loop immediately dropped it. The bucketing used `path.parse(rootPath).name`, which strips only the *last* extension, so `instrumentation.universal.ts` collapsed to `'instrumentation.universal'` — not equal to `INSTRUMENTATION_HOOK_FILENAME` (`'instrumentation'`) — and `instrumentationHookFilePath` stayed `undefined`. The hook was never registered, never compiled to `.next/server/instrumentation.js`, and `register()` silently never ran. The same shape of bug applied to `middleware.universal.ts` and `proxy.universal.ts`, since all three buckets used the same `name === FILENAME_CONSTANT` check. Reuse the three detection regexes that already filter `rootPaths` to also assign the matched files to their convention buckets, comparing against `path.parse(rootPath).base` (the full filename including extension). The filter and the bucketing now agree on the same matching rule, and a multi-segment extension in `pageExtensions` no longer silently drops the file. Added an integration test under `test/integration/instrumentation-page-extensions/` that configures `pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'universal.ts', 'universal.tsx']`, places the hook at `src/instrumentation.universal.ts`, and asserts that `.next/server/instrumentation.js` is produced — which only happens if detection succeeded. Webpack-only (gated on `IS_TURBOPACK_TEST`) mirroring the existing `build-trace-extra-entries` integration suite. Fixes vercel#92342
Contributor
|
Allow CI Workflow Run
Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Make
next builddetectinstrumentation,middleware, andproxyfiles when the user configures a multi-segmentpageExtensionsentry (e.g.'universal.ts') and names their hook accordingly:Today this hook is silently dropped — the file is never compiled to
.next/server/instrumentation.jsandregister()never runs. Same formiddleware.universal.tsandproxy.universal.ts.Why?
packages/next/src/build/index.tsbuilds three regexes fromconfig.pageExtensionsand uses them to filter root-level files intorootPaths. That part works —instrumentation.universal.tscorrectly survives the filter (the unescaped.in the extension list is treated as\.|., which still matches the literal.in the filename).The bug is in the bucketing loop right after, which iterates
rootPathsand assigns each match to one of the three target paths via:path.parse('/src/instrumentation.universal.ts').nameis'instrumentation.universal'— Node only strips the last extension. So the equality check fails and the file is dropped on the floor. The filter said yes, the bucket said no.The same pattern applies to
MIDDLEWARE_FILENAMEandPROXY_FILENAME, so any user withproxy.<custom>.ts/middleware.<custom>.tshits the identical bug.How?
Reuse the three detection regexes that already filter
rootPathsto also assign each matched file to its bucket, comparing againstpath.parse(rootPath).base(the full filename including extension). The filter and the bucketing now agree on the same matching rule:Behavior with the default
pageExtensions(['ts', 'tsx', 'js', 'jsx', 'mts', 'mjs', ...]) is unchanged — those are all single-segment, and a file namedinstrumentation.tsmatches the regex and lands in the bucket exactly as before.Scope
This fixes the detection gap reported as the primary issue (instrumentation never registered with custom multi-segment
pageExtensions). The reporter also notes a secondary observation in their case 3 (addingproxy.universal.tsmakes instrumentation register but withNEXT_RUNTIME=edge). That smells like a separate downstream runtime-routing issue and is out of scope here — once detection works correctly with this PR, that scenario is worth re-evaluating against canary.Test plan
Added
test/integration/instrumentation-page-extensions/:pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'universal.ts', 'universal.tsx'],src/instrumentation.universal.tsexportingregister(), and a minimalpages/index.tsx.nextBuild, asserts exit code 0 and that.next/server/instrumentation.jsexists — the latter only happens if detection succeeded.IS_TURBOPACK_TEST), mirroring the existingbuild-trace-extra-entriesintegration suite.Verified locally on Windows + webpack: test fails on canary without the fix and passes after it.
Fixes #92342