Important tsconfig Options Explained

Understand the most important tsconfig.json options: target, module, outDir, rootDir, strict, lib, sourceMap, and more. Learn what each one controls and how to choose the right values.

7 min read

A tsconfig.json can have over a hundred compiler options, but only a handful of tsconfig options matter when you are setting up a project. This article covers the options you need to understand first: what they control, why they matter, and how to pick the right value.

If you have not created a tsconfig yet, start with how to create a tsconfig file.

The essential six

These six options define what your project produces and how strictly it is checked:

OptionWhat it controlsCommon values
targetJavaScript version in outputES2022, ES2020
moduleModule system in outputnodenext, preserve
strictAll type-checking strictnesstrue (recommended)
outDirWhere .js files are written./dist
rootDirRoot of the source tree./src
libBuilt-in type definitionsES2022, DOM

Set these six before anything else. The rest of this article explains each one and covers a few more options you will likely need.

target: which JavaScript version to emit

The target option tells TypeScript which version of JavaScript to produce. If you write an arrow function and target ES5, TypeScript converts it to a regular function. If you target ES2015 or higher, the arrow function stays as-is.

Choose the oldest JavaScript version your runtime needs to support. For Node.js 18 and above, ES2022 is a safe choice. For modern browsers, ES2020 or higher works well. The special value ESNext means "everything this TypeScript version supports," but it can change between TypeScript upgrades, so pinning a specific version is safer.

The target option also changes the default lib. Setting target to ES2022 automatically includes type definitions for ES2022 APIs like Array.at() and Error.cause.

module: how imports and exports are emitted

The module option controls the module system TypeScript uses when emitting JavaScript. This affects how import and export statements appear in the output. Here is how different settings affect the same code:

module valueEmitted output styleBest for
nodenextESM or CJS based on package.jsonModern Node.js projects
preserveImports and exports left as-isBundled apps (Vite, Webpack)
ESNextESM import/export statementsBundled apps, modern browsers
CommonJSrequire() and module.exportsLegacy Node.js projects

For a new Node.js project, use nodenext. For a frontend app bundled with Vite or Next.js, use preserve or ESNext. Do not use CommonJS for new projects unless you have a specific reason.

strict: all type checks at once

Setting strict to true enables every strict type-checking flag simultaneously. This includes noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, and several others.

You can turn individual flags off after enabling strict. For example, if noImplicitAny is too strict during a migration, set strict to true and noImplicitAny to false. This keeps all other checks active while relaxing just one.

Starting a new project? Enable strict from day one. It is much harder to add later. For details on every check it enables, see strict mode in TypeScript.

outDir and rootDir: controlling output layout

The outDir option tells TypeScript where to write compiled .js files. Without it, .js files appear next to your .ts files, which clutters your source tree.

The rootDir option tells TypeScript where your source tree starts. This controls the directory structure inside outDir. With rootDir set to ./src and outDir set to ./dist, a file at src/utils/helpers.ts becomes dist/utils/helpers.js.

If you omit rootDir, TypeScript infers it as the longest common path of all input files. This is usually correct, but set it explicitly when your source files span multiple top-level directories.

lib: which built-in APIs are available

The lib option controls which JavaScript and environment type definitions TypeScript includes. By default, lib is derived from target, but you can override it.

For a Node.js project, you typically do not include the DOM library. For a browser project, you must include it, or TypeScript will not know about document.querySelector or addEventListener.

lib valueProvides types for
ES2022Array.at(), Error.cause, top-level await
DOMdocument, window, HTMLElement, browser APIs
DOM.IterableIterable DOM collections like NodeList
ESNextThe newest APIs your TypeScript version supports

If your runtime has polyfills for newer APIs, you can set lib higher than target. For example, target ES2020 with lib set to ES2022 plus DOM gives you type definitions for ES2022 APIs while emitting ES2020-compatible syntax.

sourceMap: debugging in the browser

The sourceMap option generates .js.map files that map compiled JavaScript back to the original TypeScript source. This lets you set breakpoints and step through .ts files directly in browser DevTools or the Node.js debugger.

Always enable this during development. It adds a small build cost but makes debugging dramatically easier. In production builds you can turn it off to reduce output size, or use inlineSourceMap if your deployment setup needs it.

skipLibCheck: faster compilation

The skipLibCheck option skips type-checking of all .d.ts declaration files. This can significantly speed up compilation, especially in projects with many dependencies.

It is safe to enable in most projects. The tradeoff is that conflicting type definitions between libraries might go unnoticed. If your build suddenly breaks after adding a dependency, try turning this off temporarily to see if the problem is a type conflict in declaration files.

moduleResolution: how imports are found

The moduleResolution option controls how TypeScript resolves import paths. You rarely need to set this explicitly because it defaults sensibly based on module, but understanding it helps when imports break:

moduleResolutionHow it resolves
nodenextNode.js ESM/CJS rules, requires file extensions in ESM
bundlerLike nodenext but does not require file extensions
node10Legacy Node.js resolution, CommonJS only

For most projects, the default is correct. If you are using a bundler and imports are failing, try setting moduleResolution to bundler.

esModuleInterop: cleaner default imports

The esModuleInterop option lets you write a default-style import for CommonJS modules, which is more natural. This is enabled by default with nodenext and preserve module settings.

If you are on an older module setting, enable it manually. It also enables allowSyntheticDefaultImports, which suppresses type errors for this pattern. Without it, importing CommonJS modules requires a namespace import syntax that is less convenient.

Putting it all together, here is a solid starting configuration for a Node.js project:

jsonjson
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "nodenext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "sourceMap": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

This enables all the important checks, separates output from source, and targets modern Node.js. For a browser project bundled with Vite, change module to preserve and add DOM to lib:

jsonjson
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "preserve",
    "lib": ["ES2022", "DOM"],
    "strict": true,
    "sourceMap": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Both configurations start with strict enabled. That single line is the most important choice in your tsconfig.

Rune AI

Rune AI

Key Insights

  • target controls the JavaScript version in emitted output; set it to match your runtime.
  • module controls the module system; use nodenext for Node.js, preserve for bundled apps.
  • strict: true enables all type-checking flags at once.
  • outDir separates compiled .js from source .ts files.
  • lib controls which built-in APIs TypeScript knows about; add dom for browser projects.
  • sourceMap enables debugging TypeScript directly in the browser or Node inspector.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between target and lib?

target controls which JavaScript syntax features are downleveled (e.g., arrow functions to regular functions). lib controls which built-in type definitions are available (e.g., Promise, Map, document). You can set a modern lib with an older target if you have polyfills.

Should I use esModuleInterop?

Yes, in almost all cases. It lets you write import React from 'react' instead of import * as React from 'react' for CommonJS modules. It is enabled by default with nodenext and preserve module settings.

What is the best module setting for a Node.js project?

Use nodenext. It supports both CommonJS and ESM and respects the type field in your package.json. For bundled frontend apps, use preserve or esnext.

Conclusion

The most important tsconfig options are target, module, strict, outDir, rootDir, and lib. Set these six correctly from the start and your project will have the right output for its runtime, the right type definitions available, and the strongest type-checking enabled.