moduleResolution: NodeNext vs Bundler (and Why It Breaks Builds)
How TypeScript resolves imports in modern toolchains, when to use NodeNext vs bundler, and how flags like verbatimModuleSyntax affect real projects.
Frontend Interview Team
March 01, 2026
What you’ll learn
- What
moduleResolutionactually controls - When
NodeNextis the correct choice - When
bundleris the correct choice - How
verbatimModuleSyntaxprevents “TypeScript hides runtime issues”
30‑second interview answer
moduleResolution tells TypeScript how to find modules and interpret package metadata (exports, type, file extensions). Use NodeNext when your runtime is Node’s ESM loader rules. Use bundler when a bundler (Vite/Webpack/Next) resolves imports in a way that can differ from Node. Mismatches cause the classic “works locally, fails in CI/prod” problems.
Mental model: TypeScript is simulating a resolver
When TS sees:
import { x } from "some-pkg";It must answer:
- Which file is that?
- Which export shape does it have?
- Is it ESM or CJS?
- Does the package
exportsmap allow this path?
Different resolvers answer differently.
When to use NodeNext
Choose moduleResolution: "NodeNext" when:
- You run output in Node (server, scripts, libraries)
- You rely on Node’s ESM rules (
type: module,.cjs/.mjs) - You want TS to respect
package.json.exportsstrictly
This catches problems early because TS behaves more like the real runtime.
When to use bundler
Choose moduleResolution: "bundler" when:
- A bundler is responsible for resolution at runtime
- You import non-TS assets via bundler plugins (css, svg, virtual modules)
- You want TS to be permissive in ways bundlers are (sometimes) permissive
Warning: If prod runtime is Node (no bundler), using bundler can hide failures.
verbatimModuleSyntax: the “stop lying to me” switch
TypeScript can remove or rewrite imports in ways that make code type-check but fail at runtime.
verbatimModuleSyntax nudges TS toward:
- Preserving import/export statements more literally
- Forcing you to mark type-only imports explicitly
Example:
// Without type-only import, TS might emit an import you don’t want.
import { User } from "./types";
// Better:
import type { User } from "./types";This matters for performance and correctness—especially in ESM.
Real-world bug: “TS compiled, Node crashed”
Scenario
- Dev: Vite (bundler resolution)
- Prod: Node script (Node resolution)
- tsconfig:
moduleResolution: "bundler"
Symptom
- Works in dev
- Fails in prod when Node enforces
exportsboundaries or extension rules
Fix
- Use
NodeNextfor Node-executed code - Or split configs: one for app (bundler), one for scripts/server (NodeNext)
Production rule of thumb
- If Node runs it, prefer
NodeNext. - If a bundler runs it,
bundlercan be fine. - In mixed repos, create two tsconfigs and be explicit.
Interview questions
-
Q: What does
moduleResolutionchange?- A: How TS looks up modules (files + package exports + conditions) and determines the correct typings.
-
Q: Why would
bundlermask issues?- A: Bundlers can allow import patterns Node rejects, so TS may accept code that later breaks in Node.
-
Q: What’s the value of
verbatimModuleSyntax?- A: It forces clearer separation of type-only imports and reduces surprises between TS output and runtime.
Quick recap
NodeNext= align TS with Node ESM behavior.bundler= align TS with bundler behavior.- Use
verbatimModuleSyntax+import typeto avoid emit surprises.