Conditional Exports (node/browser) + Correct Types
How to publish different entry points for Node vs browser (or edge) using package.json exports conditions, without breaking TypeScript consumers.
Frontend Interview Team
March 01, 2026
What you’ll learn
- What conditional exports are and why they exist
- How bundlers/Node pick different files via conditions
- How to keep
.d.tsaligned with each condition - Common failure modes and how to avoid them
30‑second interview answer
Conditional exports let a package provide different entry points depending on the environment (e.g. node vs browser). Modern tooling reads package.json.exports and chooses the best match based on conditions. In TypeScript, you must ensure the types you expose match the JS entry point chosen by the resolver; otherwise consumers get runtime behavior that doesn’t match compile-time types.
Mental model: One import, multiple possible files
A consumer writes:
import { hash } from "@acme/crypto";But the resolver might load different files:
- Browser build →
dist/browser.js - Node build →
dist/node.js
Same import, different runtime.
Example: conditional exports map
{
"name": "@acme/crypto",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"browser": "./dist/browser.js",
"node": "./dist/node.js",
"default": "./dist/node.js"
}
}
}Notes
defaultis the fallback when no condition matches.- Order matters: tooling picks the best condition it supports.
The tricky part: types must match reality
If browser.js does not support the same API as node.js, you have options:
Option A (best DX): keep the API identical
Both builds export the same functions with the same semantics as much as possible.
Option B: make environment differences explicit in types
Expose a narrower API in the browser build, but then you must provide different .d.ts per condition (advanced, easy to get wrong).
A safer pattern: export a function that reveals capability at runtime:
export function supportsNativeCrypto(): boolean;Common failure modes
1) Types point to Node implementation but browser loads browser file
- TS compiles against Node signatures
- Browser runtime uses different implementation
- Result: runtime errors or subtle behavior mismatch
2) “It works in webpack, breaks in Node”
- Bundler supports
browsercondition - Node picks
node/default
Fix: test both resolution paths (or use NodeNext in CI for Node consumers).
Production rule of thumb
- Use conditional exports only when you truly need it.
- Prefer one stable API across environments.
- Keep types aligned with the chosen runtime entry.
Interview questions
-
Q: Why do conditional exports exist?
- A: To let packages ship optimized builds per environment without changing import paths.
-
Q: What’s the TypeScript risk?
- A: The type surface can diverge from the actual runtime entry point loaded.
Quick recap
exportscan choose different files via conditions.- Types must match the runtime entry chosen.
- Prefer identical APIs across conditions for sane maintenance.