nextjs - 💡(How to fix) Fix RouteModule.prepare doesn't await ensureInstrumentationRegistered

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Fix Action

Fix / Workaround

The compiled app-route template (build/templates/app-route.ts) awaits routeModule.prepare(...), but prepare returns before the instrumentation hook finishes. On a fresh Vercel lambda, the userland POST body runs while register() is still working through its top-level await import(...) chain. ensureInstrumentationRegistered is memoized, so it does eventually finish — just not before the first request gets dispatched.

Code Example

Operating System: Linux (Vercel serverless, Node 22)
Binaries:
  Node: 22.x
  pnpm: 10.24.0
Relevant Packages:
  next: 16.2.6 (also reproduced against 16.3.0-canary.24 source)
  react: 19.x
Next.js Config:
  default Vercel build, app router

---

const { ensureInstrumentationRegistered } = await import(
  '../lib/router-utils/instrumentation-globals.external.js'
)
// ensure instrumentation is registered and pass
// onRequestError below
ensureInstrumentationRegistered(absoluteProjectDir, this.distDir)

---

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME !== "nodejs") return;
  const mod = await import("./bootstrap"); // anything after this await is racy
  globalThis.__myConfig = mod.loadConfig();
}

---

// app/api/hello/route.ts
export async function POST() {
  return Response.json({ value: globalThis.__myConfig.foo });
}

---

- ensureInstrumentationRegistered(absoluteProjectDir, this.distDir)
+ await ensureInstrumentationRegistered(absoluteProjectDir, this.distDir)
RAW_BUFFERClick to expand / collapse

RouteModule.prepare doesn't await ensureInstrumentationRegistered

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release (16.3.0-canary.24).

Provide environment information

Operating System: Linux (Vercel serverless, Node 22)
Binaries:
  Node: 22.x
  pnpm: 10.24.0
Relevant Packages:
  next: 16.2.6 (also reproduced against 16.3.0-canary.24 source)
  react: 19.x
Next.js Config:
  default Vercel build, app router

Which area(s) are affected?

Instrumentation, App Router (route handlers), Runtime

Which stage(s) are affected?

Vercel (production)

Additional context

We started seeing intermittent TypeErrors in production where module-scoped state our register() is supposed to populate was still null when an App Router POST handler ran. Six errors over an hour, all clustered with bursts of concurrent webhook deliveries to a fresh lambda. Retries always succeeded.

Tracing it down: in packages/next/src/server/route-modules/route-module.ts, RouteModule.prepare calls ensureInstrumentationRegistered without awaiting it (canary line 674, 16.2.6 line 654):

const { ensureInstrumentationRegistered } = await import(
  '../lib/router-utils/instrumentation-globals.external.js'
)
// ensure instrumentation is registered and pass
// onRequestError below
ensureInstrumentationRegistered(absoluteProjectDir, this.distDir)

The two other call sites for the same function do await it:

PathFileAwaited?
Classic Node serverpackages/next/src/server/next-server.ts:373 (via BaseServer.handleRequestprepareImpl)
Edge adapterpackages/next/src/server/web/adapter.ts:114
App Router per-request RouteModule.preparepackages/next/src/server/route-modules/route-module.ts:674

The compiled app-route template (build/templates/app-route.ts) awaits routeModule.prepare(...), but prepare returns before the instrumentation hook finishes. On a fresh Vercel lambda, the userland POST body runs while register() is still working through its top-level await import(...) chain. ensureInstrumentationRegistered is memoized, so it does eventually finish — just not before the first request gets dispatched.

The docs say "code in the register function will be executed before any other code", which matches the two awaited paths but not this one.

Reproduction

I haven't built a minimal repro app yet, but the shape is:

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME !== "nodejs") return;
  const mod = await import("./bootstrap"); // anything after this await is racy
  globalThis.__myConfig = mod.loadConfig();
}
// app/api/hello/route.ts
export async function POST() {
  return Response.json({ value: globalThis.__myConfig.foo });
}

Deploy to Vercel, hit it with ~5 concurrent POSTs against a cold endpoint. The first request to land before register() resolves will see __myConfig as null. Happy to put together a deployable repro if it would help.

Suggested fix

Just add the missing await:

- ensureInstrumentationRegistered(absoluteProjectDir, this.distDir)
+ await ensureInstrumentationRegistered(absoluteProjectDir, this.distDir)

The function memoizes a single promise per process, so the await is a microtask no-op on every request after the first. The cold-start request would block until register() is actually done, which is the behavior the docs describe.

What we're doing in the meantime

We exposed a "contexts ready" promise from our register() chain and await it at the top of each affected route handler. Works, but it's opt-in per route and easy to forget.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING