tsconfig.json Best Settings by Project Type
tsconfigtoolingcompilerproject-setup

tsconfig.json Best Settings by Project Type

TTypeScript Page Editorial
2026-06-08
9 min read

A reusable checklist for choosing the best tsconfig.json settings for Node, React, libraries, monorepos, and migration projects.

A good tsconfig.json does two jobs at once: it catches mistakes early, and it stays out of your way when you are trying to ship. The problem is that the best settings are not the same for every TypeScript project. A React app, a Node service, a published library, and a monorepo package all need slightly different compiler options. This guide gives you a reusable checklist for choosing practical tsconfig settings by project type, with copy-ready examples, notes on tradeoffs, and a short list of things to verify before you commit to a config.

Overview

If you only remember one thing, make it this: do not look for a single universal tsconfig.json example. Start with a strict baseline, then adjust the module system, emitted output, and framework-specific options to match how the project runs.

In practice, most teams benefit from treating tsconfig in layers:

  • A shared baseline for safety and readability
  • A runtime layer for Node, browser, bundler, or library output
  • A build or test override when tooling needs different settings

That approach keeps your TypeScript guide rails consistent without forcing every package to compile the same way.

Here is a sensible baseline to use as a starting point for many projects:

{
  "compilerOptions": {
    "target": "ES2022",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "useUnknownInCatchVariables": true,
    "noFallthroughCasesInSwitch": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}

Why these options?

  • strict is still the best single switch for long-term code quality.
  • noUncheckedIndexedAccess helps surface many real runtime bugs around maps, arrays, and dynamic keys.
  • exactOptionalPropertyTypes makes optional properties behave more honestly, which is useful in API and state-heavy code.
  • noImplicitOverride protects inheritance-based code from accidental method mismatches.
  • skipLibCheck often speeds up builds and avoids noise from third-party type packages. It is a pragmatic default for applications.

There is no need to turn on every possible compiler option on day one. The goal is not maximum strictness as a badge. The goal is useful strictness that your team will keep.

If you are coming from JavaScript, it can help to add strictness in phases. Start with strict: true, then add options like noUncheckedIndexedAccess after the codebase is stable enough to absorb the new findings.

Checklist by scenario

Use this section as the main decision tool. Pick the scenario closest to your project, then customize from there.

1. Node.js application or API

This is the most common setup for backend services, CLIs, workers, and scheduled jobs. Your main decisions are:

  • Which module system the runtime expects
  • Whether TypeScript should emit JavaScript or only type-check
  • How much interop you need with CommonJS packages

A practical tsconfig for Node often looks like this:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src",
    "sourceMap": true,
    "types": ["node"]
  },
  "include": ["src"]
}

Choose this when: your runtime follows modern Node module rules and you want fewer surprises between compile-time and runtime behavior.

Double-check:

  • Your package.json module settings line up with your import style.
  • Your test runner can handle the same module format.
  • You actually need emitted files. If a framework or runtime compiles for you, noEmit may be better.

For many backend teams, this is a better long-term default than older CommonJS-first configs. If you are debugging odd import or path behavior, keep your TypeScript module settings as close as possible to the real runtime.

2. React application with a bundler

A tsconfig for React is usually simpler than a Node service because the bundler handles output. TypeScript’s main job is type-checking and editor support.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "isolatedModules": true,
    "noEmit": true,
    "skipLibCheck": true,
    "allowJs": false
  },
  "include": ["src"]
}

Why these settings work well:

  • moduleResolution: "Bundler" is often a good fit for modern frontend tooling.
  • jsx: "react-jsx" matches current React JSX transform expectations in many setups.
  • isolatedModules helps keep your code compatible with transpilers that process files independently.
  • noEmit avoids confusion when a bundler already owns build output.

This setup is usually a better TypeScript guide for frontend apps than trying to reuse a Node-oriented configuration everywhere.

3. Next.js or framework-managed frontend

When a framework generates or manages some TypeScript settings, resist the urge to over-customize. The framework often has assumptions about JSX, module resolution, and emit behavior.

Your checklist here is short:

  • Keep strict enabled
  • Use noEmit unless you clearly need otherwise
  • Be careful with path aliases and confirm the framework, test runner, and editor all resolve them the same way
  • Avoid changing module-related options unless the framework docs explicitly support it

If your framework already provides a recommended tsconfig json example, treat it as the starting point and add only the strictness options you want to enforce across the codebase.

4. Published library or package

Libraries need a different mindset. Your application only has to run in your environment. A library has to be consumed by someone else’s environment.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "dist",
    "rootDir": "src",
    "stripInternal": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Priorities for libraries:

  • Emit type declarations with declaration
  • Keep public API types clean and stable
  • Use module syntax that matches your packaging strategy
  • Be conservative with language target if your consumers span multiple runtimes

If you publish more than one output format, you may want separate build-specific configs rather than forcing one file to do everything.

5. Monorepo with shared packages

Monorepos benefit from a base config plus package-level extensions. This avoids copy-paste drift and makes upgrades easier.

A common pattern looks like this:

Base config:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Package config:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src"]
}

What matters most:

  • Keep shared safety rules in one place
  • Let each package choose runtime-specific module and emit settings
  • Do not assume frontend, backend, and shared utility packages should compile identically

If you are scaling a larger codebase, this layered setup is easier to maintain than one global config with exceptions scattered everywhere. Teams working on review automation may also want to standardize config expectations across packages; if that is relevant, see Self-hosted Kodus in a TypeScript monorepo: scaling PR reviews without vendor markup.

6. JavaScript to TypeScript migration project

For a gradual migration, the best tsconfig settings are usually not the strictest ones immediately. They are the ones that help the team move forward without creating a wall of errors.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "allowJs": true,
    "checkJs": false,
    "noEmit": true,
    "skipLibCheck": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

Migration checklist:

  • Use allowJs to bring JavaScript files into the project
  • Keep checkJs off at first unless the team is ready for additional noise
  • Turn on stricter options gradually after the initial conversion
  • Track the settings you plan to tighten later so the project does not stall in a half-migrated state

This is where TypeScript best practices need some flexibility. A migration config is a transition tool, not necessarily the final destination.

What to double-check

Before you settle on a tsconfig.json, verify these points. Most frustrating TypeScript errors come from mismatches between compiler assumptions and actual tooling.

Module system and runtime

Make sure module, moduleResolution, and your runtime or bundler agree. If imports work in the editor but fail in production, this is one of the first places to inspect.

Emit strategy

Ask a simple question: who is responsible for build output?

  • If TypeScript compiles your app, configure outDir, rootDir, and source maps clearly.
  • If a framework or bundler compiles your app, prefer noEmit.

Many confusing setups come from enabling TypeScript emit in projects where another tool already owns the build.

Strictness level

Confirm that the team can realistically maintain the selected strictness options. Turning on strict flags that everyone immediately works around defeats the point.

Paths and aliases

Path aliases can improve imports, but only if all tools agree. Check:

  • TypeScript compiler
  • Bundler or runtime
  • Test runner
  • Linter
  • Editor integration

If one tool does not resolve aliases the same way, the convenience can quickly turn into a debugging tax.

Library versus application defaults

Applications can be more pragmatic. Libraries need cleaner declaration output and more conservative compatibility decisions. If you use the same tsconfig for both, something usually becomes awkward.

And when compiler diagnostics become hard to interpret, a reference like TypeScript Error Codes List: Meaning, Common Causes, and Fixes can help you separate real config issues from ordinary code errors.

Common mistakes

These are the issues that repeatedly make otherwise reasonable TypeScript starter projects harder to maintain.

Using an old copied config without understanding it

Many teams inherit a file full of options from an older boilerplate, then hesitate to remove anything. If a setting no longer serves the current toolchain, delete it. A shorter config is often a better config.

Optimizing for edge cases instead of daily workflow

Choose compiler options that make normal work safer and clearer. Do not over-design around a rare build target if it makes every edit, test run, or onboarding experience more complex.

Turning off strictness too quickly

When TypeScript feels noisy, the first reaction is often to weaken the config. Sometimes that is reasonable, especially in migrations. But often the better fix is to narrow types earlier, improve shared utility types, or tighten an API boundary.

Assuming frontend and backend configs should match

They share some principles, but not necessarily the same module resolution, emit strategy, JSX handling, or environment types.

Ignoring generated files, tests, or scripts

Not every TypeScript file belongs under one config. Build scripts, test setup files, and application code may need different assumptions. Separate configs are often cleaner than one overloaded file.

Overusing path aliases

A few clear aliases can help. Too many can hide project structure and create friction across tools. Use them to reduce real pain, not to redesign every import statement.

When to revisit

Your tsconfig is not set once and forgotten. It is worth revisiting whenever the project’s runtime, build pipeline, or maintenance goals change. A short review at the right moment can prevent months of low-grade friction.

Revisit your settings when:

  • You upgrade TypeScript and want to adopt safer defaults or cleaner module behavior
  • You switch bundlers, test runners, or deployment targets
  • You move from CommonJS assumptions to modern ESM-oriented workflows
  • You split an app into packages or merge packages into a monorepo
  • You start publishing internal code as a reusable library
  • You begin a JavaScript to TypeScript migration phase
  • Developers repeatedly hit confusing import, path, or build errors

A practical review process looks like this:

  1. List the actual runtime and build tools involved.
  2. Mark which tool emits output and which tools only type-check.
  3. Confirm the module system in package.json and TypeScript match.
  4. Decide whether strictness should stay the same, increase, or temporarily loosen for a migration.
  5. Test the config in editor, local build, test suite, and CI before treating it as final.

If you want a lightweight rule of thumb, use this one: whenever your workflow changes, revisit your compiler options before the old assumptions harden into team habits.

For teams formalizing standards, this can also fit into onboarding and internal training. A shared explanation of why your config exists is often as important as the config itself. That is especially true in larger organizations with mixed project types; see Design an in-house TypeScript learning path that actually moves the needle for a broader process perspective.

The best tsconfig.json is not the most advanced one. It is the one that matches the project you have today, leaves room for the project you expect tomorrow, and is clear enough that the next developer can understand it without reverse-engineering a stack of historical decisions.

Related Topics

#tsconfig#tooling#compiler#project-setup
T

TypeScript Page Editorial

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-08T18:50:16.857Z