* feat(instrumentation): support nextConfig.instrumentationClientInject (#1326)
Add support for the Next.js instrumentationClientInject config option that
lets users inject client bootstrap modules for side effects and an optional
onRouterTransitionStart hook ahead of the user's instrumentation-client file.
Changes:
- New generateInstrumentationClientInjectModule() generates a virtual ESM
module with side-effect imports in array order + composed hook fan-out.
- Accept instrumentationClientInject in NextConfig / ResolvedNextConfig.
- New vinext:instrumentation-client-inject Vite plugin with resolveId/load
hooks; virtual module string is precomputed once in config() for efficiency.
- Unit tests cover empty injects, single/multi specifiers, hook ordering,
and empty-module fallback when no user file exists.
* test(instrumentation): fix oxfmt formatting and assertion indentation
The test expected 2-space indent for the generated code, but the
implementation produces 4-space indent within if blocks. Also fixes
oxfmt quote style and line wrapping.
* fix(ci): lint warnings and missing field in test helper
- Replace unnecessary escaped quotes inside template literals to fix
eslint(no-useless-escape) warnings in instrumentation-client-inject.ts
- Add missing instrumentationClientInject field to makeResolved() test
helper in next-config.test.ts to fix TS2322 type errors
* Address review feedback for instrumentationClientInject.
Make clientInjectModule self-clearing on config re-eval, filter non-string inject specifiers, document dual resolution paths, and add escaping coverage.
Signed-off-by: Divanshu Chauhan <divkix@divkix.me>
* test(instrumentation): add plugin pipeline integration tests and fix inject resolution
Add client-environment tests for instrumentationClientInject virtual module
wiring, omit the alias when injects are active so resolveId/load run, reload
next.config when root changes, and pre-resolve relative inject paths. Share
empty-module path via INSTRUMENTATION_CLIENT_EMPTY_MODULE and simplify tests.
* fix(instrumentation): resolve empty-module.js in production builds
CI E2E builds failed because INSTRUMENTATION_CLIENT_EMPTY_MODULE pointed
at empty-module.ts, which is not shipped in dist. Prefer .js when present.
* test(instrumentation): address PR review comments for client inject
- Revert nextConfigRoot guard changes in config loading to avoid buildId divergence risk
- Add cache-busting to dynamic ESM module imports in tests and document the reason
- Add integration test case for empty-inject and missing user-file fallback path
---------
Signed-off-by: Divanshu Chauhan <divkix@divkix.me>
The 25-line __ensureInstrumentation block in entries/app-rsc-entry.ts was
module-level state plus idempotent init logic embedded in a generated entry
template. This is the canonical 'imperative shell with mutable bookkeeping'
pattern — it never belongs in codegen.
Lift it to a new server/instrumentation-runtime.ts module exporting
ensureInstrumentationRegistered(instrumentationModule). The generated RSC
entry now only:
1. Imports the helper (conditional on instrumentationPath).
2. Calls await ensureInstrumentationRegistered(_instrumentation) in the
request handler.
All bookkeeping (initialized flag, shared promise, prerender skip) lives in
the typed helper, making it unit-testable and keeping the entry template thin.
New tests cover idempotency, concurrent racing, prerender skipping, and
graceful handling of modules without register/onRequestError.
Refs:
- Next.js instrumentation docs: https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
- Next.js source: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/dev/next-dev-server.ts (instrumentation boot)
- Next.js source: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-relayer.ts (onRequestError global wiring)
* chore: migrate to vite plus
* Disable typeAware and typeCheck
* Update CI
* Fix CI
* Fix test
* Clean
* Run test with vp
* Try revert
* react: false In test
* Fix test
* Revert "Try revert"
This reverts commit 009da10473.
* Update
* Update
* Try revert ci changes
* revert
* Run vp migrate
* Disable typeAware and typeCheck for now
* Better resolve for test
* Use vp dev instead of vite
* Update expect
* Fix NormalizeManifestModuleId
* Try increase timeout
* Update to use vp
* Try new check
* Bring back npx vp
* Migrate CI
* Make next-intl resolvable
* Update
* Update
* Update
On Cloudflare Workers the isolate can terminate after the Response is
sent, killing in-flight error reports (e.g. Sentry HTTP requests).
Fix by having reportRequestError() self-register with
ctx.waitUntil() via getRequestExecutionContext(). This is the same
pattern used by KVCacheHandler, ISR regeneration, and fetch cache
revalidation. On Node.js (dev or vinext start) the context is null
so it's a no-op — fire-and-forget is fine because the process stays
alive.
Since reportRequestError never rejects (internal try/catch), the
redundant .catch() at all 5 call sites is removed.
* add oxfmt formatter: config, scripts, CI, editor setup, docs
* rebuild lockfile
* fix: add Format to required checks list, remove dead ignore pattern
* run fmt
* add format to agents.md again
* fix(config): pass correct Next.js phase to function-form configs
unwrapConfig() hardcoded "phase-development-server" regardless of
whether vinext was running in dev or build mode. Apps that branch on
the phase argument in next.config.ts received wrong config during
production builds.
Map Vite's env.command to the correct Next.js phase constant:
- "serve" → "phase-development-server"
- "build" → "phase-production-build"
* test: add instrumentation module test coverage
The instrumentation module had zero tests. Add 11 tests covering:
- findInstrumentationFile: file discovery across root and src/,
priority ordering, null when absent
- runInstrumentation: register() lifecycle, onRequestError handler
storage, graceful handling of empty modules, error isolation
when ssrLoadModule fails
- reportRequestError: handler invocation with correct args,
no-op without handler, error isolation when handler throws
* fix(test): use shared helper for App Router static export test
The static-export test had its own startFixtureServer that loaded the
fixture's vite.config.ts via configFile. This caused RSC plugin timing
issues on Node 25 / macOS — the App Router dev server returned 404 for
all pages. The Pages Router section (same approach) worked because it
doesn't use the RSC plugin.
Switch the App Router section to use the shared startFixtureServerHelper
from helpers.ts, which uses configFile: false with explicit plugin
registration. This matches how app-router.test.ts (193 passing tests)
creates its dev server and avoids the RSC timing issue.
* fix: address PR review feedback
- Import PHASE_PRODUCTION_BUILD and PHASE_DEVELOPMENT_SERVER constants
from shims/constants.ts instead of hardcoding string literals in
index.ts and config/next-config.ts
- Add afterEach cleanup for temp directories in instrumentation tests
* refactor: use phase constants in tests, simplify pure function test setup
- Replace hardcoded phase strings in next-config.test.ts with
PHASE_PRODUCTION_BUILD and PHASE_DEVELOPMENT_SERVER constants
- Remove unnecessary vi.resetModules() from findInstrumentationFile
tests — it's a pure function with no module-level state to reset
* refactor: use shared startFixtureServer helper in static export tests
Remove the local startFixtureServer function — the shared helper from
tests/helpers.ts does the same thing. The Pages Router fixture's
vite.config.ts is just `vinext()` with no special options, so the
shared helper (which uses configFile: false + vinext plugin) works
identically.