nextjs - 💡(How to fix) Fix Regression in 16.2.0: turbopackInferModuleSideEffects breaks Bun runtime ("Bun is not defined" on cold start) [4 comments, 4 participants]

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…
GitHub stats
vercel/next.js#91691Fetched 2026-04-08 01:02:28
View on GitHub
Comments
4
Participants
4
Timeline
10
Reactions
0
Author
Timeline (top)
commented ×4closed ×2labeled ×2locked ×1

Next.js 16.2.0 introduced a regression where serverless functions that use Bun runtime APIs (Bun.* globals) fail with ReferenceError: Bun is not defined at module evaluation time on cold start. The failure affects all tRPC/API routes in the affected Lambda bundle simultaneously, making the deployment non-functional on every cold start.

Critically, this also causes the Vercel platform to detect and assign different runtimes to different routes within the same deployment — some routes correctly show Bun 1.x while others fall back to Node.js 22.x, despite bunVersion: "1.x" being set globally in vercel.json. This is directly observable in the Vercel deployment function list:

/en/help/[categorySlug]                   Bun 1.x      42.3 MB  syd1
/en/help/[categorySlug]/[articleSlug]     Bun 1.x      42.3 MB  syd1
/en/health/[parentCategorySlug]           Bun 1.x      42.3 MB  syd1

/en/health/endocrinology                  Node.js 22.x 45.6 MB  syd1
/en/health/ent                            Node.js 22.x 45.6 MB  syd1
/en/health/general-health                 Node.js 22.x 45.6 MB  syd1
/en/health/infectious-diseases            Node.js 22.x 45.6 MB  syd1
/en/health/mental-health                  Node.js 22.x 45.6 MB  syd1
/en/health/neurology                      Node.js 22.x 45.6 MB  syd1

Routes whose Lambda bundle includes a module with a top-level Bun global call (due to the new side-effect inference forcing it into the bundle) are detected as Node.js 22.x. Routes whose bundles are clean fall through correctly as Bun 1.x. In 16.1.6, all routes were consistently Bun 1.x.

The lambdaRuntimeStats metadata returned by Vercel also reflects this — deployments on 16.1.6 show {"bun":4}, deployments on 16.2.0 show {"nodejs":4} or mixed {"bun":4,"nodejs":2} depending on which route bundles pulled in the affected module.

Root Cause

PR #87216 changed packages/next/src/server/config-shared.ts:

- turbopackInferModuleSideEffects: !isStableBuild(),
+ turbopackInferModuleSideEffects: true,

This was explicitly gated to canary-only in PR #87215 before the 16.1 stable release. Re-enabling it unconditionally in 16.2.0 activates turbopack's AST-based side-effect inference for all builds.

The mechanism: The AST analyzer in turbopack-ecmascript/src/analyzer/side_effects.rs walks top-level statements and marks any module containing a top-level call expression as SideEffectful. Previously, turbopack would tree-shake unused transitive imports. With infer_module_side_effects: true, any module with a top-level call that is re-exported via a barrel (even if the specific export consuming the call is not used by the consumer) now gets bundled and evaluated at Lambda cold start.

The call chain:

config-shared.ts (default: true)
  → config-schema.ts
  → crates/next-core/src/next_config.rs (.unwrap_or(true))
  → next_server/context.rs (EcmascriptOptionsContext.infer_module_side_effects)
  → turbopack-ecmascript/src/references/mod.rs (three-tier side-effect check)
  → turbopack-ecmascript/src/analyzer/side_effects.rs (AST walk)

Fix Action

Fix / Workaround

Workarounds

Code Example

/en/help/[categorySlug]                   Bun 1.x      42.3 MB  syd1
/en/help/[categorySlug]/[articleSlug]     Bun 1.x      42.3 MB  syd1
/en/health/[parentCategorySlug]           Bun 1.x      42.3 MB  syd1

/en/health/endocrinology                  Node.js 22.x 45.6 MB  syd1
/en/health/ent                            Node.js 22.x 45.6 MB  syd1
/en/health/general-health                 Node.js 22.x 45.6 MB  syd1
/en/health/infectious-diseases            Node.js 22.x 45.6 MB  syd1
/en/health/mental-health                  Node.js 22.x 45.6 MB  syd1
/en/health/neurology                      Node.js 22.x 45.6 MB  syd1

---

- turbopackInferModuleSideEffects: !isStableBuild(),
+ turbopackInferModuleSideEffects: true,

---

config-shared.ts (default: true)
  → config-schema.ts
  → crates/next-core/src/next_config.rs (.unwrap_or(true))
  → next_server/context.rs (EcmascriptOptionsContext.infer_module_side_effects)
  → turbopack-ecmascript/src/references/mod.rs (three-tier side-effect check)
  → turbopack-ecmascript/src/analyzer/side_effects.rs (AST walk)

---

// packages/ui/src/server/redis/client.ts
export const redis = getRedisClient() // calls Bun.redis internally

---

export { redis, getRedisClient } from "./client"
export { createRedisClient } from "./factory" // <-- only this is actually consumed

---

experimental: {
  turbopackInferModuleSideEffects: false,
}

---

// Before — top-level call, flagged as SideEffectful by AST analyzer
export const redis = getRedisClient()

// After — Proxy, no top-level call, correctly tree-shakeable
export const redis: BunRedis = new Proxy({} as BunRedis, {
  get(_, prop: string | symbol) {
    return Reflect.get(getRedisClient(), prop as string)
  },
})
RAW_BUFFERClick to expand / collapse

Summary

Next.js 16.2.0 introduced a regression where serverless functions that use Bun runtime APIs (Bun.* globals) fail with ReferenceError: Bun is not defined at module evaluation time on cold start. The failure affects all tRPC/API routes in the affected Lambda bundle simultaneously, making the deployment non-functional on every cold start.

Critically, this also causes the Vercel platform to detect and assign different runtimes to different routes within the same deployment — some routes correctly show Bun 1.x while others fall back to Node.js 22.x, despite bunVersion: "1.x" being set globally in vercel.json. This is directly observable in the Vercel deployment function list:

/en/help/[categorySlug]                   Bun 1.x      42.3 MB  syd1
/en/help/[categorySlug]/[articleSlug]     Bun 1.x      42.3 MB  syd1
/en/health/[parentCategorySlug]           Bun 1.x      42.3 MB  syd1

/en/health/endocrinology                  Node.js 22.x 45.6 MB  syd1
/en/health/ent                            Node.js 22.x 45.6 MB  syd1
/en/health/general-health                 Node.js 22.x 45.6 MB  syd1
/en/health/infectious-diseases            Node.js 22.x 45.6 MB  syd1
/en/health/mental-health                  Node.js 22.x 45.6 MB  syd1
/en/health/neurology                      Node.js 22.x 45.6 MB  syd1

Routes whose Lambda bundle includes a module with a top-level Bun global call (due to the new side-effect inference forcing it into the bundle) are detected as Node.js 22.x. Routes whose bundles are clean fall through correctly as Bun 1.x. In 16.1.6, all routes were consistently Bun 1.x.

The lambdaRuntimeStats metadata returned by Vercel also reflects this — deployments on 16.1.6 show {"bun":4}, deployments on 16.2.0 show {"nodejs":4} or mixed {"bun":4,"nodejs":2} depending on which route bundles pulled in the affected module.

Root Cause

PR #87216 changed packages/next/src/server/config-shared.ts:

- turbopackInferModuleSideEffects: !isStableBuild(),
+ turbopackInferModuleSideEffects: true,

This was explicitly gated to canary-only in PR #87215 before the 16.1 stable release. Re-enabling it unconditionally in 16.2.0 activates turbopack's AST-based side-effect inference for all builds.

The mechanism: The AST analyzer in turbopack-ecmascript/src/analyzer/side_effects.rs walks top-level statements and marks any module containing a top-level call expression as SideEffectful. Previously, turbopack would tree-shake unused transitive imports. With infer_module_side_effects: true, any module with a top-level call that is re-exported via a barrel (even if the specific export consuming the call is not used by the consumer) now gets bundled and evaluated at Lambda cold start.

The call chain:

config-shared.ts (default: true)
  → config-schema.ts
  → crates/next-core/src/next_config.rs (.unwrap_or(true))
  → next_server/context.rs (EcmascriptOptionsContext.infer_module_side_effects)
  → turbopack-ecmascript/src/references/mod.rs (three-tier side-effect check)
  → turbopack-ecmascript/src/analyzer/side_effects.rs (AST walk)

Reproduction

Setup:

  • Vercel deployment with bunVersion: "1.x" in vercel.json
  • bundlePagesRouterDependencies: true in next.config.js
  • A workspace package (e.g. @internal/ui) in transpilePackages that is NOT in serverExternalPackages
  • That package has a file with a top-level call expression using a Bun global, e.g.:
// packages/ui/src/server/redis/client.ts
export const redis = getRedisClient() // calls Bun.redis internally
  • A barrel index.ts that re-exports this alongside other exports:
export { redis, getRedisClient } from "./client"
export { createRedisClient } from "./factory" // <-- only this is actually consumed

Steps:

  1. next 16.1.6 → deploy → works, all routes show Bun 1.x runtime, no cold start failures
  2. Bump next to 16.2.0 → deploy → routes whose Lambda bundle includes the affected module fall back to Node.js 22.x, those routes fail with ReferenceError: Bun is not defined on every cold start

Why this worked in 16.1.6: turbopack tree-shook client.ts out of the serverless bundle because infer_module_side_effects was false (gated to canary). The barrel's redis export was never bundled since only createRedisClient (from ./factory) was consumed downstream.

Why it breaks in 16.2.0: With infer_module_side_effects: true, turbopack correctly identifies export const redis = getRedisClient() as a top-level side effect and marks client.ts as SideEffectful. The module is now bundled into every Lambda that imports anything from the barrel, and evaluated on cold start — but in a context where Bun is not available, causing the platform to fall back to Node.js 22.x for those routes.

Expected Behavior

Enabling turbopackInferModuleSideEffects should not change which runtime (Bun vs Node.js) a module's top-level code is evaluated in, nor cause split runtime detection across routes within a single deployment. If a deployment is configured to use Bun runtime via bunVersion: "1.x", all routes should use Bun 1.x consistently.

Alternatively, the tree-shaking should correctly eliminate the redis export from bundles where only createRedisClient is consumed, regardless of whether side-effect inference is enabled.

Workarounds

Option A (immediate): Revert to 16.1.6 or set in next.config.js:

experimental: {
  turbopackInferModuleSideEffects: false,
}

Option B (correct long-term): Remove top-level call expressions that use Bun globals from re-exported modules. e.g. replace eager initialization with a lazy Proxy:

// Before — top-level call, flagged as SideEffectful by AST analyzer
export const redis = getRedisClient()

// After — Proxy, no top-level call, correctly tree-shakeable
export const redis: BunRedis = new Proxy({} as BunRedis, {
  get(_, prop: string | symbol) {
    return Reflect.get(getRedisClient(), prop as string)
  },
})

Environment

  • Next.js: 16.2.0 (regression from 16.1.6)
  • Bundler: Turbopack (--turbopack flag in build command)
  • Deployment: Vercel with bunVersion: "1.x"
  • Config: bundlePagesRouterDependencies: true, workspace package in transpilePackages
  • Router: Pages Router

Related

  • PR #87216 — the change that enabled this for stable builds
  • PR #87215 — the change that gated it to canary for 16.1
  • PR #86398 — original feature implementation
  • Issue #75220 — prior Turbopack/Bun compatibility issue (closed)

extent analysis

Fix Plan

To resolve the issue, you can use one of the following approaches:

Option A: Disable Turbopack Side-Effect Inference

Set turbopackInferModuleSideEffects to false in your next.config.js:

experimental: {
  turbopackInferModuleSideEffects: false,
}

Option B: Remove Top-Level Call Expressions

Replace eager initialization with a lazy Proxy:

// Before — top-level call, flagged as SideEffectful by AST analyzer
export const redis = getRedisClient()

// After — Proxy, no top-level call, correctly tree-shakeable
export const redis: BunRedis = new Proxy({} as BunRedis, {
  get(_, prop: string | symbol) {
    return Reflect.get(getRedisClient(), prop as string)
  },
})

Verification

After applying the fix, verify that:

  • All routes use the Bun 1.x runtime consistently.
  • The lambdaRuntimeStats metadata returned by Vercel shows only {"bun":4}.
  • The deployment functions list in Vercel shows the correct runtime for all routes.

Extra Tips

To prevent similar issues in the future:

  • Monitor your deployment's runtime detection and lambdaRuntimeStats metadata.
  • Regularly review your code for top-level call expressions that may be causing issues with Turbopack's side-effect inference.
  • Consider using lazy initialization or Proxies to avoid top-level calls whenever possible.

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

nextjs - 💡(How to fix) Fix Regression in 16.2.0: turbopackInferModuleSideEffects breaks Bun runtime ("Bun is not defined" on cold start) [4 comments, 4 participants]