Link to the code that reproduces this issue
https://github.com/Git-I985/next-custom-extensions-breaks-instrumentation
To Reproduce
Baseline (works correctly)
next.config.ts — no pageExtensions:
const nextConfig: NextConfig = {
output: 'standalone',
};
src/instrumentation.ts:
export function register() {
// @ts-ignore
const runtime = typeof EdgeRuntime === 'string' ? 'edge' : 'nodejs';
console.log(`[instrumentation] register() called in ${runtime} (NEXT_RUNTIME=${process.env.NEXT_RUNTIME})`);
}
npm i && rm -rf .next && docker compose up --build --force-recreate
Result: register() is called, logs NEXT_RUNTIME=nodejs. ✅
Reproducing the bug
next.config.ts — add custom pageExtensions:
const nextConfig: NextConfig = {
output: 'standalone',
pageExtensions: ['tsx', 'ts', 'universal.ts', 'universal.tsx'],
};
Rename src/instrumentation.ts → src/instrumentation.universal.ts (same content).
npm i && rm -rf .next && docker compose up --build --force-recreate
Result: register() is never called. ❌
Now add proxy.universal.ts at the project root or src folder
npm i && rm -rf .next && docker compose up --build --force-recreate
Result: register() is called, but logs NEXT_RUNTIME=edge (why not node?). ❌
Current vs. Expected behavior
| Scenario |
Current |
Expected |
instrumentation.ts (default ext) |
✅ Called with NEXT_RUNTIME=nodejs |
✅ |
instrumentation.universal.ts, no proxy |
❌ Not called at all |
Should be called with NEXT_RUNTIME=nodejs |
instrumentation.universal.ts + proxy.universal.ts |
❌ Called, but NEXT_RUNTIME=edge |
Should be called with NEXT_RUNTIME=nodejs |
instrumentation.universal.ts + proxy.ts |
❌ Not called (.ts is in pageExtensions) |
Should be called |
Expected: The file extension used for instrumentation should have no effect on:
- Whether the file is executed
- Which runtime (
NEXT_RUNTIME) it executes in
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6041
Available memory (MB): 49152
Available CPU cores: 12
Binaries:
Node: 24.2.0
npm: 11.3.0
Yarn: N/A
pnpm: N/A
Relevant Packages:
next: 16.0.10 // There is a newer version (16.2.2) available, upgrade recommended!
eslint-config-next: N/A
react: 19.2.3
react-dom: 19.2.3
typescript: 5.9.3
Next.js Config:
output: standalone
⚠ There is a newer version (16.2.2) available, upgrade recommended!
Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
Read more - https://nextjs.org/docs/messages/opening-an-issue
Deployment: output: 'standalone', Docker
Which area(s) are affected? (Select all that apply)
Instrumentation, Middleware, Runtime, Not sure
Which stage(s) are affected? (Select all that apply)
next build (local), Other (Deployed)
Additional context
Kinda two distinct but related bugs are observed:
Bug 1 — Extension affects proxy requirement.
With a custom extension, instrumentation.universal.ts is silently ignored unless a matching proxy.universal.ts is present. Meanwhile, proxy.ts does not work as a trigger even though .ts is explicitly listed in pageExtensions.
Bug 2 — Extension forces Edge runtime.
Once the proxy file is in place and instrumentation.universal.ts is executed, NEXT_RUNTIME is always edge — regardless of the actual server runtime context. Standard instrumentation.ts correctly reports NEXT_RUNTIME=nodejs.
The pageExtensions config is documented as controlling which file extensions are treated as pages/routes. It should have no side-effects on the instrumentation lifecycle or the runtime environment reported to register().
Link to the code that reproduces this issue
https://github.com/Git-I985/next-custom-extensions-breaks-instrumentation
To Reproduce
Baseline (works correctly)
next.config.ts— nopageExtensions:src/instrumentation.ts:Result:
register()is called, logsNEXT_RUNTIME=nodejs. ✅Reproducing the bug
next.config.ts— add custompageExtensions:Rename
src/instrumentation.ts→src/instrumentation.universal.ts(same content).Result:
register()is never called. ❌Now add
proxy.universal.tsat the project root orsrcfolderResult:
register()is called, but logsNEXT_RUNTIME=edge(why not node?). ❌Current vs. Expected behavior
instrumentation.ts(default ext)NEXT_RUNTIME=nodejsinstrumentation.universal.ts, no proxyNEXT_RUNTIME=nodejsinstrumentation.universal.ts+proxy.universal.tsNEXT_RUNTIME=edgeNEXT_RUNTIME=nodejsinstrumentation.universal.ts+proxy.ts.tsis inpageExtensions)Expected: The file extension used for
instrumentationshould have no effect on:NEXT_RUNTIME) it executes inProvide environment information
Which area(s) are affected? (Select all that apply)
Instrumentation, Middleware, Runtime, Not sure
Which stage(s) are affected? (Select all that apply)
next build (local), Other (Deployed)
Additional context
Kinda two distinct but related bugs are observed:
Bug 1 — Extension affects proxy requirement.
With a custom extension,
instrumentation.universal.tsis silently ignored unless a matchingproxy.universal.tsis present. Meanwhile,proxy.tsdoes not work as a trigger even though.tsis explicitly listed inpageExtensions.Bug 2 — Extension forces Edge runtime.
Once the proxy file is in place and
instrumentation.universal.tsis executed,NEXT_RUNTIMEis alwaysedge— regardless of the actual server runtime context. Standardinstrumentation.tscorrectly reportsNEXT_RUNTIME=nodejs.The
pageExtensionsconfig is documented as controlling which file extensions are treated as pages/routes. It should have no side-effects on the instrumentation lifecycle or the runtime environment reported toregister().