Let's be honest - we've all been there. You're working on a TypeScript project, everything's going great, and then you open that tsconfig.json file. Suddenly you're staring at a labyrinth of cryptic options, and you just copy-paste something from Stack Overflow and pray it works. tsconfig.json Sound familiar? Here's the thing: tsconfig doesn't have to be intimidating. Behind all those options, there are really just a few essential settings you need to understand. The rest? They're there when you need them, but you can safely ignore them until that day comes. This isn't just another tsconfig guide. This is the guide I wish I had when I started with TypeScript – clear, practical, and bs-free. The Essential tsconfig Properties (For Any Project) These five properties form the foundation of any solid TypeScript configuration: 1. target - What Version of JavaScript to Generate target This tells TypeScript what "version" of JavaScript to create when it converts your TypeScript code. "target": "ES2022" "target": "ES2022" Why it matters: Modern JavaScript (newer versions) has cool features that make your code cleaner and faster, but older browsers might not understand them. Why it matters: When to change it: When to change it: Use "ES2022" or newer for modern environments (Node.js, modern browsers) Use "ES6" if you need to support slightly older browsers Use "ES5" only if you need to support really old browsers (Internet Explorer) Use "ES2022" or newer for modern environments (Node.js, modern browsers) "ES2022" Use "ES6" if you need to support slightly older browsers "ES6" Use "ES5" only if you need to support really old browsers (Internet Explorer) "ES5" Problem it solves: Ensures your code runs in your target environment without needing complex build setups. Problem it solves: 2. module - How Modules Connect with Each Other module This tells TypeScript how to handle import and export statements in your code. import export "module": "NodeNext" "module": "NodeNext" Why it matters: Different environments handle modules differently, and the wrong setting can break your app entirely. Why it matters: When to change it: When to change it: For Node.js: Use "NodeNext" for modern Node projects For browsers with bundlers (webpack/Vite): Use "ESNext" For direct browser usage: Use "ES2015" or "ESNext" For Node.js: Use "NodeNext" for modern Node projects "NodeNext" For browsers with bundlers (webpack/Vite): Use "ESNext" "ESNext" For direct browser usage: Use "ES2015" or "ESNext" "ES2015" "ESNext" Problem it solves: Makes sure your imports and exports work correctly in your target environment. Problem it solves: 3. moduleResolution - How TypeScript Finds Imported Files moduleResolution This tells TypeScript how to locate files when you write import { something } from 'somewhere'. import { something } from 'somewhere' "moduleResolution": "NodeNext" "moduleResolution": "NodeNext" Why it matters: If TypeScript can't find your imports, you'll get frustrating errors. Why it matters: When to change it: When to change it: For Node.js projects: Use "NodeNext" For projects using bundlers: Use "Bundler" Older projects might use "Node" (classic Node.js resolution) For Node.js projects: Use "NodeNext" "NodeNext" For projects using bundlers: Use "Bundler" "Bundler" Older projects might use "Node" (classic Node.js resolution) "Node" Problem it solves: Prevents "Cannot find module" errors and ensures imports resolve correctly. Problem it solves: 4. strict - Catch More Errors Before They Happen strict This turns on TypeScript's strict checking, which is like having a very picky code reviewer built into your editor. "strict": true "strict": true Why it matters: Catches many common bugs before your code even runs. Why it matters: When to change it: When to change it: New projects: Always use true Converting a JavaScript project: You might start with false and gradually move to true New projects: Always use true true Converting a JavaScript project: You might start with false and gradually move to true false true Problem it solves: Prevents entire categories of bugs like null reference errors, undefined variables, and implicit type conversions. Problem it solves: 5. include - Which Files to Process include Tells TypeScript which files to check and compile. "include": ["src/**/*"] "include": ["src/**/*"] Why it matters: Helps TypeScript focus only on the files that matter, making compilation faster. Why it matters: When to change it: When to change it: When your source files are in different folders When you want to exclude certain files When your source files are in different folders When you want to exclude certain files Problem it solves: Speeds up compilation and prevents TypeScript from processing files you don't want it to. Problem it solves: Essential Properties for Node.js Projects When building a Node.js application, these settings are particularly important: 1. outDir - Where to Put Generated Files outDir Tells TypeScript where to put the JavaScript files it creates. "outDir": "dist" "outDir": "dist" Why it matters: Keeps your source code separate from compiled output, making your project cleaner. Why it matters: Problem it solves: Prevents compiled code from cluttering your source directories, making it easier to deploy only what's needed. Problem it solves: 2. esModuleInterop - Play Nice with CommonJS and ESM esModuleInterop Makes it easier to use packages that use the older CommonJS format with modern ESM imports. "esModuleInterop": true "esModuleInterop": true Why it matters: Node.js has two module systems (CommonJS and ESM), and this helps them work together. Why it matters: Problem it solves: Prevents bizarre import errors when mixing package types, especially with older libraries. Problem it solves: 3. resolveJsonModule - Import JSON Files Directly resolveJsonModule Lets you import JSON files as if they were TypeScript/JavaScript modules. "resolveJsonModule": true "resolveJsonModule": true Why it matters: Makes working with configuration and data files much simpler. Why it matters: Problem it solves: Eliminates the need for workarounds to import JSON data in your Node.js applications. Problem it solves: Essential Properties for React Projects React projects with bundlers (like webpack/Vite/esbuild/Next.js) have different needs: 1. jsx - Handling React's JSX Syntax jsx Tells TypeScript how to process React's special JSX syntax. "jsx": "react-jsx" "jsx": "react-jsx" Why it matters: React components use JSX, which needs to be transformed into regular JavaScript. Why it matters: Problem it solves: Ensures your React components compile correctly and integrate with your bundler. Problem it solves: 2. noEmit - Let the Bundler Handle Output noEmit Tells TypeScript to only check your code for errors but not generate any output files. "noEmit": true "noEmit": true Why it matters: In React projects, your bundler (webpack/Vite) handles the code transformation, not TypeScript. Why it matters: Problem it solves: Prevents duplicate processing and avoids conflicts between TypeScript and your bundler. Problem it solves: 3. isolatedModules - Ensuring Transpiler Compatibility isolatedModules Makes TypeScript warn you if you use features that only work with the TypeScript compiler but would break with simpler transpilers. "isolatedModules": true "isolatedModules": true When you need it: When using bundlers like webpack, Vite, or transpilers like Babel or swc that process files individually. When you need it: Problem it solves: Prevents mysterious build errors when your code moves from development to production bundling by ensuring your TypeScript code is compatible with any transpilation setup. Problem it solves: Why Node.js and React Configurations Differ One crucial thing to understand: TypeScript doesn't work alone in modern JavaScript development. TypeScript doesn't work alone in modern JavaScript development. In a Node.js application, TypeScript is both the type-checker and the compiler. It directly transforms your TypeScript code into JavaScript that Node.js can run. This means your tsconfig settings directly affect the output code. [TypeScript Files] → [TypeScript Compiler] → [JavaScript Files] → [Node.js Runtime] [TypeScript Files] → [TypeScript Compiler] → [JavaScript Files] → [Node.js Runtime] In a React application, the workflow is different: [TypeScript Files] → [TypeScript Type-Checking] → [Bundler (webpack/Vite/etc.)] → [JavaScript Bundle] → [Browser] [TypeScript Files] → [TypeScript Type-Checking] → [Bundler (webpack/Vite/etc.)] → [JavaScript Bundle] → [Browser] In this scenario, TypeScript primarily serves as a type-checker, and the bundler handles the actual transformation to JavaScript. This is why many React project configs include "noEmit": true - TypeScript isn't producing the final JavaScript output. This is why many React project configs include "noEmit": true The Important Distinction Here's a key insight many developers miss: in bundler-based workflows, many of your tsconfig settings become suggestions rather than requirements. The bundler may: many of your tsconfig settings become suggestions rather than requirements Override your module settings completely Handle path aliases differently Ignore your output directory Apply its own transformations Override your module settings completely Handle path aliases differently Ignore your output directory Apply its own transformations This is why your React/Vite configuration has different settings than your Node.js configuration. It's not just a different target environment; it's a fundamentally different build process. For React applications, your bundler config (vite.config.js, webpack.config.js, etc.) typically has the final say on: How modules are processed Where output files go Which files are included/excluded How assets are handled How modules are processed Where output files go Which files are included/excluded How assets are handled For example esbuild ignores almost all tsconfig settings, with the exception of a few. ignores almost all tsconfig settings This is why the React tsconfig recipe above focuses on type-checking settings and leaves many transformation details to the bundler. Important But Optional Properties These properties aren't essential for every project, but they solve specific problems you might encounter: 1. skipLibCheck - Speed Up Build Time skipLibCheck Tells TypeScript to not thoroughly check the types in library files (node_modules). "skipLibCheck": true "skipLibCheck": true When you need it: When your builds are taking forever because of large dependencies. When you need it: Problem it solves: Dramatically speeds up TypeScript compilation by trusting that your dependencies have correct types. Problem it solves: 3. baseUrl and paths - Cleaner Import Paths baseUrl paths Lets you create shortcuts for import paths so you can write cleaner imports. "baseUrl": ".", "paths": { "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"] } "baseUrl": ".", "paths": { "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"] } When you need it: When you're tired of writing ../../../../ in your import statements. When you need it: ../../../../ Problem it solves: Makes imports cleaner and more maintainable, especially in larger projects. Problem it solves: 4. declaration and declarationMap - Publishing Libraries declaration declarationMap Generates type definition files so others can use your code with TypeScript. "declaration": true, "declarationMap": true "declaration": true, "declarationMap": true When you need it: When you're building a library for others to use. When you need it: Problem it solves: Makes your library TypeScript-friendly for consumers. Problem it solves: 5. lib - Controlling Available APIs lib Tells TypeScript which built-in objects and APIs your code can use. "lib": ["DOM", "DOM.Iterable", "ESNext"] "lib": ["DOM", "DOM.Iterable", "ESNext"] When you need it: When you want to control exactly which browser/JavaScript features your code can access. When you need it: Problem it solves: Prevents your code from using features that aren't available in your target environment. Problem it solves: Ready-to-Use Configurations Instead of struggling to piece together the right configuration, here are ready-to-use configurations for common project types. Consider these your starting templates, not the final word. Node.js API/Backend Application { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules", "**/*.test.ts"] } { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules", "**/*.test.ts"] } React Frontend Application (with Vite/webpack) { "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "isolatedModules": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules"] } { "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "isolatedModules": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules"] } Here’s more inspirations with tsconfig setups tsconfig/bases. tsconfig/bases Troubleshooting Common tsconfig Problems Let's look at some common errors you might encounter and how to fix them with tsconfig: 1. "Cannot Find Module" Errors TS2307: Cannot find module 'some-package' or its corresponding type declarations. TS2307: Cannot find module 'some-package' or its corresponding type declarations. What's happening: TypeScript can't find the module you're trying to import. What's happening: Potential fixes: Potential fixes: Check if moduleResolution is correct for your environment Make sure esModuleInterop is set to true If it's an npm package, install its type definitions with npm install @types/package-name --save-dev If it's your own module, check if the path is included in your include paths Check if moduleResolution is correct for your environment moduleResolution Make sure esModuleInterop is set to true esModuleInterop true If it's an npm package, install its type definitions with npm install @types/package-name --save-dev npm install @types/package-name --save-dev If it's your own module, check if the path is included in your include paths include The detective approach: These errors often have multiple potential causes. Instead of random tweaking, follow this systematic approach: The detective approach: First, check if the package exists in node_modules Next, look for type definitions (either bundled or in @types) Then verify your import path is correct Finally, check your tsconfig module settings First, check if the package exists in node_modules Next, look for type definitions (either bundled or in @types) Then verify your import path is correct Finally, check your tsconfig module settings 2. Path Alias Not Working TS2307: Cannot find module '@components/Button' or its corresponding type declarations. TS2307: Cannot find module '@components/Button' or its corresponding type declarations. What's happening: Your path aliases aren't being resolved correctly. What's happening: Potential fixes: Potential fixes: Make sure you've set baseUrl and paths correctly If using a bundler, ensure it's also configured with the same aliases Check that the referenced files are included in your include patterns Make sure you've set baseUrl and paths correctly baseUrl paths If using a bundler, ensure it's also configured with the same aliases Check that the referenced files are included in your include patterns include Common gotcha: Path aliases need configuration in both tsconfig.json AND your bundler (webpack/Vite). Forgetting either side will cause mysterious errors where things compile but break at runtime. Common gotcha: 3. Type Errors in Third-Party Libraries TS2339: Property 'someProperty' does not exist on type... TS2339: Property 'someProperty' does not exist on type... What's happening: TypeScript is finding type errors in your node_modules. What's happening: Potential fixes: Potential fixes: Set skipLibCheck to true to ignore errors in declaration files Install proper type definitions for the package In the worst case, create a declaration file to override the types Set skipLibCheck to true to ignore errors in declaration files skipLibCheck true Install proper type definitions for the package In the worst case, create a declaration file to override the types Conclusion: tsconfig Isn't Scary Anymore Here's the truth: most TypeScript projects only need 5-7 key tsconfig options to work correctly. The rest are there for specialized cases. Instead of memorizing all the options or blindly copying configurations, focus on understanding these core properties and what problems they solve. Over time, you'll develop an intuition for which properties you need to adjust when you encounter specific issues. Remember: Start with one of the recipe configurations for your project type Understand the essential properties and why they matter Add specialized properties only when you encounter specific problems Start with one of the recipe configurations for your project type Understand the essential properties and why they matter Add specialized properties only when you encounter specific problems With this approach, you'll never be intimidated by a tsconfig file again. You'll approach each new TypeScript project with confidence, knowing exactly which options matter and why. The next time someone on your team says "I don't understand what's happening with TypeScript," send them this guide. We all deserve to work with TypeScript without the configuration anxiety.