TypeScript Best Practices

Chapter 1 · Practice 2 of 6

Choose module and moduleResolution for the runtime that runs your code

From Compiler settings and TypeScript 7

Why it matters

Module settings tell TypeScript how imports are resolved at runtime. If they do not match reality, the compiler accepts imports that fail when the code runs, or rejects imports that would work.

  • Code that Node.js runs directly (servers, CLIs, libraries): use "module": "nodenext". TypeScript then follows Node's real rules for ESM and CommonJS, including required file extensions in ESM imports.
  • Code that a bundler builds (Vite, webpack, Next.js and similar): use "moduleResolution": "bundler" with "module": "esnext" or "preserve". Bundlers allow extensionless imports and exports conditions that Node alone does not.

The old moduleResolution: node (also called node10) and classic were deprecated in TypeScript 6.0 and are errors in TypeScript 7.0, as are module values amd, umd, systemjs and none. If an old project still uses them, moving to nodenext or bundler is the first step of any upgrade.

Do

  • Use nodenext for anything Node.js loads without a bundler
  • Use bundler resolution for apps a bundler builds
  • Prefer package.json subpath imports (#/...) over custom aliases when Node runs the code

Avoid

  • Keeping moduleResolution: node or node10: it is an error in TypeScript 7
  • Copying a tsconfig from a frontend app into a Node service unchanged