The tsconfig.json file controls TypeScript's compiler behavior through options like strict mode, module resolution, target output, path aliases, and project references for organizing large codebases into independently compilable units.
strict: true enables strictNullChecks, noImplicitAny, and other checks that catch real bugs — it is non-negotiable for production TypeScript projects.
Use 'bundler' moduleResolution for frontend projects (Vite/webpack) and 'nodenext' for Node.js projects — misconfiguration is the most common source of 'cannot find module' errors.
Monorepo packages declare dependencies via references with composite: true, enabling tsc --build to compile in dependency order with incremental caching.
Modern setups use SWC/esbuild for fast type-stripping and tsc --noEmit for separate type-checking, because type-checking is the slow part of compilation.
TypeScript configuration through tsconfig.json determines how your code is type-checked, what JavaScript output is generated, and how modules are resolved. Understanding these options is essential for setting up projects correctly, debugging mysterious type errors, and optimizing compilation performance.
The configuration file has several top-level fields: compilerOptions (the main configuration), include (which files to compile, e.g., ["src/**/*"]), exclude (which to skip, e.g., ["node_modules", "dist"]), extends (inherit from a base config), and references (for project references in monorepos). The extends field is powerful for sharing configuration — packages like @tsconfig/recommended, @tsconfig/node20, and @tsconfig/next provide well-tested defaults.
strict: true enables all strict checks — this is non-negotiable for production projects. It activates strictNullChecks (null/undefined are distinct types), noImplicitAny (all values must have types), strictFunctionTypes (contravariant parameter checking), and several other checks that catch real bugs.
target sets the JavaScript version of the output (e.g., ES2022). This determines which syntax features are downleveled — with ES2022, optional chaining and nullish coalescing are preserved. module sets the module system for output (ESNext for ES modules, CommonJS for Node.js require). moduleResolution determines how TypeScript finds modules — bundler (for Vite/webpack projects) or nodenext (for Node.js projects that use package.json exports).
The paths option creates import aliases: "@/*": ["./src/*"] lets you write import { Button } from '@/components/Button' instead of relative paths like ../../../components/Button. Path aliases require a baseUrl setting and must be configured in both tsconfig.json and your bundler (Vite's resolve.alias, webpack's resolve.alias).
For monorepos, project references (references + composite: true) let TypeScript understand the dependency graph between packages. Each package has its own tsconfig.json, and references declares which packages it depends on. tsc --build compiles packages in dependency order and caches results — only recompiling packages whose sources changed. This enables fast incremental compilation in large monorepos.
This strict option (recommended for modern projects) requires explicit import type for type-only imports: import type { User } from './types'. Without it, TypeScript guesses whether an import is type-only and may emit runtime imports for types that do not exist at runtime, causing errors. verbatimModuleSyntax makes the developer's intent explicit.
This option makes indexed access (obj[key], arr[i]) return T | undefined instead of T, forcing you to handle the possibility that a key does not exist. This catches a common class of runtime errors where code assumes an array index or object key is always present.
Misconfigured moduleResolution causes "Cannot find module" errors — use bundler for frontend projects and nodenext for Node.js projects. Forgetting to set jsx: react-jsx (the new JSX transform) means you must import React in every file. Setting skipLibCheck: true speeds up compilation by skipping type-checking of .d.ts files in node_modules, but can mask type errors in dependencies.
TypeScript's compiler does two things: type-checking and code emission. Modern setups separate these — SWC or esbuild handles fast code emission (stripping types), while tsc --noEmit handles type-checking. Understanding this separation explains why you see both tsc and a bundler in project scripts, and why tsconfig.json matters even when SWC does the actual compilation.
Fun Fact
TypeScript's strict mode was not available at launch — it was gradually assembled from individual flags over several releases. The strict flag was added in TypeScript 2.3 (2017) as a convenient way to enable all strict checks at once, and new strict checks added in future versions are automatically included under the strict umbrella.