TypeScript Configuration

The One Flag That Found 200 Bugs

Monday morning code review. A senior developer suggested: "Let's enable strict mode."

I hesitated. Our codebase was 35,000 lines. Surely enabling strict mode would create thousands of errors we'd spend weeks fixing?

I created a branch and added one line to tsconfig.json:

{
  "compilerOptions": {
    "strict": true
  }
}

Ran tsc. Held my breath.

227 errors.

Not thousands. Just 227. And they were all real bugs:

// Before strict mode - compiled fine
function getUserName(user) {  // Implicit any
  return user.name.toUpperCase();  // Crashes if user is null!
}

// Error found by strict mode:
// ✗ Parameter 'user' implicitly has an 'any' type

We fixed all 227 errors in 3 days. Deployed to staging.

Result: Caught 14 production bugs before they happened. Null reference errors: gone. Implicit any footguns: eliminated.

That one flag – "strict": true – was the highest ROI config change we ever made.

This article covers tsconfig.json options that prevent bugs, improve code quality, and make TypeScript actually useful.


Basic tsconfig.json

Minimal configuration to get started.

Bare Minimum

What Each Option Does

  • target: JavaScript version to output (ES5, ES2015, ES2020, ESNext)

  • module: Module system (commonjs, es2015, esnext)

  • outDir: Where compiled files go

  • rootDir: Source code location

  • include: Files to compile

  • exclude: Files to ignore


Strict Mode Options

Enable all strict type-checking options at once.

Enables all of:

  • noImplicitAny

  • strictNullChecks

  • strictFunctionTypes

  • strictBindCallApply

  • strictPropertyInitialization

  • noImplicitThis

  • alwaysStrict

Individual Strict Options

You can enable them individually:


noImplicitAny

Require explicit types, no implicit any.

Without noImplicitAny (Dangerous)

With noImplicitAny


strictNullChecks

null and undefined are not assignable to other types.

Without strictNullChecks

With strictNullChecks


strictPropertyInitialization

Class properties must be initialized.

Without strictPropertyInitialization

With strictPropertyInitialization


Module Options

Configure module system and resolution.

module and moduleResolution

Common combinations:

esModuleInterop

Allow importing CommonJS modules as ES modules.


Target and Lib

target

JavaScript version to compile to.

Impact on output:

lib

Include type definitions for runtime features.

Common combinations:


Path Configuration

baseUrl and paths

Warning: Only affects TypeScript. Need bundler or path rewriter for runtime.


Source Maps

sourceMap

Generate .map files for debugging.

Enables debugging TypeScript in browser/Node:

Debugger shows original TypeScript, not compiled JavaScript.

inlineSourceMap

Embed source map in output file.

Larger files, but no separate .map files.


Declaration Files

declaration

Generate .d.ts files for libraries.

Essential for publishing npm packages:


Additional Checks

Extra checks beyond strict mode.

noUnusedLocals and noUnusedParameters

noImplicitReturns

All code paths must return a value.

noFallthroughCasesInSwitch

Switch cases must break or return.


Project References

For monorepos and large projects.

Basic Setup

Build with references:


Real-World Configs

Node.js Server

React App

NPM Library


Common Mistakes I Made

1. Not Enabling Strict Mode

Bad:

Result: TypeScript becomes JavaScript with extra steps.

Good:

2. Wrong Target for Environment

Bad (Node.js with ES5 target):

Result: Slower code, larger bundles, polyfills for features Node has natively.

Good:

3. Including Test Files in Build

Bad:

Good:


Your Challenge

Create optimized tsconfig for different environments:

Requirements:

  1. Base config with shared options

  2. Server config:

    • Node.js target

    • CommonJS modules

    • Strict mode

    • Source maps

  3. Client config:

    • Browser target

    • ES modules

    • React support

    • Path aliases

  4. Shared types usable by both

Bonus: Add project references for incremental builds.


Key Takeaways

  1. Enable strict mode - catches real bugs

  2. Match target to runtime - don't over-transpile

  3. Use path aliases - cleaner imports (with bundler)

  4. Generate source maps - debuggable code

  5. Project references - faster builds in monorepos

  6. Additional checks - noUnusedLocals, noImplicitReturns

  7. Extend base config - share common settings

  8. skipLibCheck - faster builds, ignore .d.ts errors


What I Learned

Enabling strict: true found 227 errors in our codebase.

Every single one was a real bug:

  • Null reference errors waiting to crash

  • Type mismatches silently passing through

  • Uninitialized properties causing undefined behavior

3 days to fix them all. 14 production bugs prevented.

The lesson: TypeScript without strict mode is glorified documentation. It catches typos, but not logic errors.

With strict mode:

Without strict mode:

Configuration isn't just settings - it's your safety net.

The strictest config is the kindest to your future self. Enable strict: true on day one. Every error caught at compile time is a bug prevented in production.

Make the compiler your ally. Configure it to be ruthless.


Next: Type Assertions and Narrowing

In the next article, we'll explore type assertions and advanced narrowing techniques. You'll learn when to override the compiler and when that's a terrible idea.

Last updated