nextjs - ✅(Solved) Fix Dynamic imports not shaken from static export when constant condition is imported from another file [1 pull requests, 1 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#92082Fetched 2026-04-08 01:48:23
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
labeled ×2issue_type_added ×1

PR fix notes

PR #90300: Turbopack: cross-module constants

Description (problem / solution / changelog)

Closes https://github.com/vercel/next.js/issues/92082

This is now a proper compile-time constant:

import { IS_DEV } from './other'
if (IS_DEV) { // statically evaluates to `true`
  console.log('x')
} else {
  require("library") // not bundled
}
console.log(IS_DEV); // is replaced with console.log(true);
// other.ts
export const SOME_VALUE = 'x'

const node_env = process.env.NODE_ENV
const development_ent = 'development'
export const IS_DEV = node_env === development_ent

You can use code to compute constants just fine, and use any existing constants such as process.env.NODE_ENV. Currently, you can't use imports to other constants modules, but we can add that later on.

We can't perform this constants check for every single import, so you need to either

  • have UPPER_CASE import names as in the example above
  • or use import { lower } from './other' with { turbopackConstants: 'true' }

Then that referenced module will be analyzed for constants, and if the referenced export is a constant, it will participate in constant inlining just as process.env.FOO.

This also works fine with barrel imports, you can still do import { IS_DEV } from './barrel.js' and it will find the constants.js file which in itself will indeed only have constants exports.

Because it's not always opt-in at the import site, we can't automatically make non-constant exports an error. For that you have to add 'use turbopack constants' at the top of the module, which will make it an error if any constant import references that module, and the module has any non-constant exports:

error - [analysis] /turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/input/other.constants.js:8:7 
Export NO_CONSTANT is not a constant
  
       4 | const development_ent = 'development'
       5 | 
       6 | export const IS_DEV = node_env === development_ent
       7 | 
         +        v--------------------------------v
       8 + export const NO_CONSTANT = globalThis.foo
         +        ^--------------------------------^
       9 | 
  
  It was analyzed to be FreeVar(globalThis)["foo"]
  
  
  Import trace:
    test:
      ./turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/input/other.constants.js
      ./turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/input/index.js

Some prior art: https://rspack.rs/blog/announcing-1-5#const-inline-optimization, https://rspack.rs/config/optimization#optimizationinlineexports

No compile-time impact on a big app:

canary dfbc3dc6b7: 
438.11s user, 68.35s system, 801% cpu, 1:03.22 total
439.44s user, 71.47s system, 756% cpu, 1:07.56 total
440.32s user, 68.57s system, 750% cpu, 1:07.81 total

constants 2d7eb8220d298133212813b7267a5847eed03a22
433.22s user, 65.44s system, 800% cpu, 1:02.32 total
440.52s user, 67.68s system, 770% cpu, 1:05.95 total
433.13s user, 69.47s system, 776% cpu, 1:04.74 total

Changed files

  • turbopack/crates/turbopack-ecmascript/src/analyzer/builtin.rs (modified, +16/-4)
  • turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs (modified, +13/-12)
  • turbopack/crates/turbopack-ecmascript/src/analyzer/imports.rs (modified, +50/-14)
  • turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs (modified, +128/-18)
  • turbopack/crates/turbopack-ecmascript/src/analyzer/well_known.rs (modified, +11/-1)
  • turbopack/crates/turbopack-ecmascript/src/directive.rs (added, +51/-0)
  • turbopack/crates/turbopack-ecmascript/src/lib.rs (modified, +1/-0)
  • turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs (modified, +7/-0)
  • turbopack/crates/turbopack-ecmascript/src/references/cross_module_constants.rs (added, +331/-0)
  • turbopack/crates/turbopack-ecmascript/src/references/mod.rs (modified, +74/-39)
  • turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/locals/module.rs (modified, +23/-4)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/class_super/graph-effects.snapshot (modified, +1/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/createRequire/graph-effects.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/createRequire/graph.snapshot (modified, +1/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/default-args/graph.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/esbuild-reduced/graph.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/esbuild/graph.snapshot (modified, +3/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/iife-2/graph-effects.snapshot (modified, +5/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/iife-2/graph.snapshot (modified, +1/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/imports/graph.snapshot (modified, +3/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/issue-75938/graph-effects.snapshot (modified, +5/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/issue-75938/graph.snapshot (modified, +3/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/issue-77083/graph-effects.snapshot (modified, +3/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/object/graph.snapshot (modified, +4/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/op-assign/graph.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/pack-2236/graph.snapshot (modified, +1/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/pack-2682/graph-effects.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/pack-2682/graph.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/path-join/graph-effects.snapshot (modified, +1/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/path-join/graph.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/pattern-assignment/graph.snapshot (modified, +5/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/unreachable-break/graph-effects.snapshot (modified, +6/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/webpack-target-node/graph-effects.snapshot (modified, +2/-0)
  • turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/webpack-target-node/graph.snapshot (modified, +4/-0)
  • turbopack/crates/turbopack-resolve/src/ecmascript.rs (modified, +36/-6)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/input/index.js (added, +8/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/input/other.js (added, +1/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/options.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/output/ad3e4_tests_snapshot_comptime_cross-module-attribute_input_index_5a9cdba6.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/output/bf321_tests_snapshot_comptime_cross-module-attribute_input_index_5a9cdba6.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/output/bf321_tests_snapshot_comptime_cross-module-attribute_input_index_5d65d18f.js (added, +15/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-attribute/output/bf321_tests_snapshot_comptime_cross-module-attribute_input_index_5d65d18f.js.map (added, +6/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/input/index.js (added, +18/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/input/library/constants.js (added, +1/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/input/library/index.js (added, +4/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/input/library/package.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/input/library/runtime.js (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/input/other.js (added, +10/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/issues/__l___Module not found____c__ Can't resolve __c_'.-e730c7.txt (added, +21/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/options.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-barrel_input_26b88884._.js (added, +49/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-barrel_input_26b88884._.js.map (added, +8/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/output/ad3e4_tests_snapshot_comptime_cross-module-barrel_input_index_9c9865f8.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-barrel/output/bf321_tests_snapshot_comptime_cross-module-barrel_input_index_9c9865f8.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/input/index.js (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/input/other.js (added, +6/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/options.json (added, +4/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/output/5c1d0_turbopack-tests_tests_snapshot_comptime_cross-module-cycle_input_index_4e539257.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-cycle_input_418e833a._.js (added, +27/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-cycle_input_418e833a._.js.map (added, +7/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-cycle/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-cycle_input_index_4e539257.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/input/index.js (added, +20/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/input/other.js (added, +7/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/input/third.js (added, +2/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/options.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-imported_input_b966c626._.js (added, +50/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-imported_input_b966c626._.js.map (added, +8/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/output/ad3e4_tests_snapshot_comptime_cross-module-imported_input_index_afa01604.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-imported/output/bf321_tests_snapshot_comptime_cross-module-imported_input_index_afa01604.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/input/index.js (added, +11/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/input/other.js (added, +8/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/issues/__l___Module not found____c__ Can't resolve __c_'.-f6c738.txt (added, +19/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/options.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/output/ad3e4_tests_snapshot_comptime_cross-module-long-literals_input_index_658b7cff.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/output/bf321_tests_snapshot_comptime_cross-module-long-literals_input_aabffdf8._.js (added, +41/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/output/bf321_tests_snapshot_comptime_cross-module-long-literals_input_aabffdf8._.js.map (added, +7/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-long-literals/output/bf321_tests_snapshot_comptime_cross-module-long-literals_input_index_658b7cff.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/input/index.js (added, +31/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/input/other.js (added, +15/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/issues/__l_Export __c_MISSING__ doesn't exist in target m-8c1c1e.txt (added, +20/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/issues/__l_Export __c_NO_CONSTANT__ is not a constant__-c52656.txt (added, +24/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/options.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-strict_input_48986a82._.js (added, +56/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module-strict_input_48986a82._.js.map (added, +7/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/output/ad3e4_tests_snapshot_comptime_cross-module-strict_input_index_c1bbecdc.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module-strict/output/bf321_tests_snapshot_comptime_cross-module-strict_input_index_c1bbecdc.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/input/index.js (added, +17/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/input/other.js (added, +8/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/options.json (added, +3/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/output/5c1d0_turbopack-tests_tests_snapshot_comptime_cross-module_input_index_e77d43b0.js (added, +5/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module_input_index_27c5aa60.js (added, +21/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module_input_index_27c5aa60.js.map (added, +6/-0)
  • turbopack/crates/turbopack-tests/tests/snapshot/comptime/cross-module/output/780ce_turbopack-tests_tests_snapshot_comptime_cross-module_input_index_e77d43b0.js.map (added, +5/-0)
  • turbopack/crates/turbopack-tracing/Cargo.toml (modified, +1/-0)

Code Example

import dynamic from "next/dynamic";

const isDevelopment = process.env.NODE_ENV === "development";

const DevOnlyComponent = isDevelopment ? dynamic(() => import("...")) : () => null;

---

/* lib/env.ts */
export const isDevelopment = process.env.NODE_ENV === "development";

/* app/page.tsx */
import dynamic from "next/dynamic";
import { isDevelopment } from "@/lib/env";

// this does not work---content is not rendered, but still present in the output bundle
const DevOnlyComponent = isDevelopment ? dynamic(() => import("...")) : () => null;

---

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Fri Jul  5 17:56:39 PDT 2024; root:xnu-10063.141.1~2/RELEASE_ARM64_T8122
  Available memory (MB): 24576
  Available CPU cores: 8
Binaries:
  Node: 23.11.1
  npm: 11.12.0
  Yarn: 1.22.22
  pnpm: 10.25.0
Relevant Packages:
  next: 16.2.1 // Latest available version is detected (16.2.1).
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: export
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/ysulyma/nextjs-dynamic-tree-shaking

To Reproduce

Run npm install and then jest to see the issue. I am only expecting dynamic component with isDevelopment imported from another file to pass, but there is also a test for the "dream syntax" I would really like to use.

Current vs. Expected behavior

I am using this pattern

import dynamic from "next/dynamic";

const isDevelopment = process.env.NODE_ENV === "development";

const DevOnlyComponent = isDevelopment ? dynamic(() => import("...")) : () => null;

to exclude devtools from my production bundle. This works as written, but breaks if I move isDevelopment into another file and import it.

/* lib/env.ts */
export const isDevelopment = process.env.NODE_ENV === "development";

/* app/page.tsx */
import dynamic from "next/dynamic";
import { isDevelopment } from "@/lib/env";

// this does not work---content is not rendered, but still present in the output bundle
const DevOnlyComponent = isDevelopment ? dynamic(() => import("...")) : () => null;

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Fri Jul  5 17:56:39 PDT 2024; root:xnu-10063.141.1~2/RELEASE_ARM64_T8122
  Available memory (MB): 24576
  Available CPU cores: 8
Binaries:
  Node: 23.11.1
  npm: 11.12.0
  Yarn: 1.22.22
  pnpm: 10.25.0
Relevant Packages:
  next: 16.2.1 // Latest available version is detected (16.2.1).
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: export

Which area(s) are affected? (Select all that apply)

Output, Turbopack

Which stage(s) are affected? (Select all that apply)

next build (local)

Additional context

No response

extent analysis

Fix Plan

To fix the issue with Next.js dynamic imports and tree shaking, we need to ensure that the isDevelopment variable is evaluated at build time, not at runtime.

Here are the steps to fix the issue:

  • Move the isDevelopment check to a separate file that can be evaluated at build time.
  • Use the next/config module to access environment variables at build time.

Example Code

// lib/config.ts
import { NextConfig } from 'next';

export const isDevelopment = (config: NextConfig) => config.mode === 'development';
// app/page.tsx
import dynamic from "next/dynamic";
import { isDevelopment } from "@/lib/config";

const DevOnlyComponent = () => {
  if (isDevelopment({ mode: process.env.NODE_ENV })) {
    return dynamic(() => import("..."));
  }
  return () => null;
};

However, the above approach still won't work because dynamic import needs to be used at the top level.

A better approach is to use a wrapper component that conditionally renders the dynamic component:

// components/DevOnlyComponent.tsx
import dynamic from "next/dynamic";

const DevComponent = dynamic(() => import("..."));

const DevOnlyComponent = () => {
  if (process.env.NODE_ENV !== "development") {
    return null;
  }
  return <DevComponent />;
};

export default DevOnlyComponent;

Then use this component in your page:

// app/page.tsx
import DevOnlyComponent from "@/components/DevOnlyComponent";

const Page = () => {
  return (
    <div>
      <DevOnlyComponent />
    </div>
  );
};

This way, the dynamic component is only rendered in development mode, and it's excluded from the production bundle.

Verification

To verify that the fix worked, run npm run build and check the production bundle for the presence of the devtools code. It should not be included.

Extra Tips

  • Make sure to use the next/config module to access environment variables at build time.
  • Use a separate file for the isDevelopment check to ensure it's evaluated at build time.
  • Use a wrapper component to conditionally render the dynamic component.

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