Skip to main content
Technology & EngineeringBuild Tools240 lines

Tsconfig

TypeScript compiler configuration, project references, and tsconfig best practices

Quick Summary17 lines
You are an expert in TypeScript compiler configuration, tsconfig.json authoring, and project structuring for JavaScript and TypeScript codebases.

## Key Points

- Enable `strict: true` on every project. It enables a collection of important checks including `strictNullChecks`, `noImplicitAny`, and `strictFunctionTypes`.
- Use `noUncheckedIndexedAccess` to force handling of possibly-undefined values from array and record indexing.
- Set `verbatimModuleSyntax` (TypeScript 5.0+) to enforce explicit `import type` for type-only imports. This ensures bundlers can reliably tree-shake types.
- Use `moduleResolution: "bundler"` for projects built with Vite, Webpack, or esbuild. Use `"Node16"` for pure Node.js projects.
- Set `isolatedModules: true` to ensure compatibility with single-file transpilers (esbuild, SWC, Babel) that cannot perform cross-file type analysis.
- Use `skipLibCheck: true` to speed up compilation by not type-checking declaration files in `node_modules`.
- Use project references with `composite: true` for monorepos to enable incremental builds with `tsc --build`.
- **Path aliases not resolved at runtime**: TypeScript `paths` only affect type resolution. You need bundler aliases or a tool like `tsc-alias` to rewrite imports in emitted JavaScript.
- **moduleResolution mismatch**: Using `"bundler"` resolution with a Node.js runtime (without a bundler) will cause resolution failures. Node.js requires `"Node16"` or `"NodeNext"`.
- **Missing include/exclude**: Without `include`, TypeScript processes all `.ts` files in the directory tree, including `node_modules` or generated files. Always set `include` explicitly.
- **composite without declaration**: Project references require `composite: true`, which implicitly enables `declaration`. If you set `declaration: false` in a composite project, it will error.
skilldb get build-tools-skills/TsconfigFull skill: 240 lines
Paste into your CLAUDE.md or agent config

TypeScript Compiler Configuration — Build Tools

You are an expert in TypeScript compiler configuration, tsconfig.json authoring, and project structuring for JavaScript and TypeScript codebases.

Core Philosophy

Overview

The TypeScript compiler (tsc) uses tsconfig.json to control type checking, module resolution, output targets, and project structure. Proper configuration is essential for type safety, build performance, and compatibility with bundlers. TypeScript 5.x introduced verbatimModuleSyntax, bundler module resolution, and configuration inheritance improvements.

Setup & Configuration

Recommended Modern tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "jsx": "react-jsx",

    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "forceConsistentCasingInFileNames": true,
    "verbatimModuleSyntax": true,

    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,

    "skipLibCheck": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,

    "outDir": "./dist",
    "rootDir": "./src",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Node.js Backend tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "lib": ["ES2022"],

    "strict": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,

    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",

    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Library tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2020"],

    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,

    "outDir": "./dist",
    "rootDir": "./src",

    "skipLibCheck": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

Core Patterns

Configuration Inheritance

// tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "moduleResolution": "bundler",
    "skipLibCheck": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  }
}

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

Project References (Monorepo)

// tsconfig.json (root)
{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/ui" },
    { "path": "./packages/server" }
  ]
}

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

// packages/ui/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "references": [{ "path": "../core" }],
  "include": ["src"]
}

Build with: tsc --build (or tsc -b) to incrementally compile referenced projects.

Separate Config for Tests

// tsconfig.test.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": true,
    "types": ["vitest/globals"]
  },
  "include": ["src", "tests"]
}

Path Aliases with Bundler Support

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@utils/*": ["./src/utils/*"]
    }
  }
}

Note: paths only tells TypeScript how to resolve types. Your bundler (Vite, Webpack, etc.) must also be configured with matching aliases.

Best Practices

  • Enable strict: true on every project. It enables a collection of important checks including strictNullChecks, noImplicitAny, and strictFunctionTypes.
  • Use noUncheckedIndexedAccess to force handling of possibly-undefined values from array and record indexing.
  • Set verbatimModuleSyntax (TypeScript 5.0+) to enforce explicit import type for type-only imports. This ensures bundlers can reliably tree-shake types.
  • Use moduleResolution: "bundler" for projects built with Vite, Webpack, or esbuild. Use "Node16" for pure Node.js projects.
  • Set isolatedModules: true to ensure compatibility with single-file transpilers (esbuild, SWC, Babel) that cannot perform cross-file type analysis.
  • Use skipLibCheck: true to speed up compilation by not type-checking declaration files in node_modules.
  • Use project references with composite: true for monorepos to enable incremental builds with tsc --build.

Common Pitfalls

  • Path aliases not resolved at runtime: TypeScript paths only affect type resolution. You need bundler aliases or a tool like tsc-alias to rewrite imports in emitted JavaScript.
  • moduleResolution mismatch: Using "bundler" resolution with a Node.js runtime (without a bundler) will cause resolution failures. Node.js requires "Node16" or "NodeNext".
  • Missing include/exclude: Without include, TypeScript processes all .ts files in the directory tree, including node_modules or generated files. Always set include explicitly.
  • esModuleInterop confusion: This flag adds helpers for importing CommonJS modules with default import syntax. It is needed for import express from 'express' to work but adds a small runtime overhead. With verbatimModuleSyntax, you may need to use import * as express from 'express' instead.
  • composite without declaration: Project references require composite: true, which implicitly enables declaration. If you set declaration: false in a composite project, it will error.
  • Stale build output: When using tsc --build, incremental builds may use stale .tsbuildinfo files. Delete dist/ and .tsbuildinfo files when encountering unexplainable type errors after config changes.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add build-tools-skills

Get CLI access →