TypeScript Configuration Patterns
Quick Guide: Shared TypeScript strict config in
packages/typescript-config/. Enablestrict: trueplusnoUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverride. Use modern module settings:module: "preserve",moduleResolution: "bundler",verbatimModuleSyntax: true,moduleDetection: "force". Use${configDir}(TS 5.5+) for portable paths. Sync path aliases between tsconfig and your build tool.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST enable TypeScript strict mode (strict: true) in ALL tsconfig.json files - non-negotiable)
(You MUST use verbatimModuleSyntax: true to enforce explicit import type - replaces deprecated importsNotUsedAsValues)
(You MUST use shared config pattern (packages/typescript-config/) in monorepos - never duplicate configs per package)
(You MUST sync path aliases between tsconfig.json and your build tool - mismatches cause import resolution failures)
(You MUST use modern module settings: module: "preserve", moduleResolution: "bundler" for bundler-based projects)
</critical_requirements>
Auto-detection: TypeScript config, tsconfig.json, tsconfig, strict mode, noUncheckedIndexedAccess, exactOptionalPropertyTypes, verbatimModuleSyntax, moduleDetection force, module preserve, moduleResolution bundler, configDir, path aliases, typescript-config, shared config, noImplicitOverride, isolatedDeclarations, erasableSyntaxOnly, import defer
When to use:
- Setting up TypeScript strict mode in new or existing projects
- Creating shared tsconfig patterns for monorepo consistency
- Configuring TS 5.x+ modern module settings (preserve, bundler, verbatimModuleSyntax)
- Syncing path aliases between tsconfig and your build tool
- Creating specialized configs (React, Node.js, library publishing)
- Migrating from deprecated TypeScript options
- Evaluating new TS features (
isolatedDeclarations,erasableSyntaxOnly,configDir,import defer)
When NOT to use:
- Runtime TypeScript code patterns (see language/framework skills)
- Linter configuration (separate skill)
- Build tool configuration (separate skill) - but DO keep path alias sync guidance here
- Daily coding conventions like naming and imports (see CLAUDE.md)
Key patterns covered:
- Shared strict config base with monorepo extension pattern
- Modern module settings (TS 5.x+: preserve, bundler, verbatimModuleSyntax, moduleDetection)
${configDir}template variable for portable shared configs (TS 5.5+)- Path alias sync between tsconfig and build tools
- Specialized configs (react.json, node.json, library.json)
isolatedDeclarationsfor parallel build support (TS 5.5+)erasableSyntaxOnlyfor Node.js direct execution (TS 5.8+)import deferfor deferred module evaluation (TS 5.9+)- TypeScript 6.0 new defaults and deprecations
Detailed resources:
- examples/core.md - Full config examples, specialized configs, TS 5.x+ features
- reference.md - Decision frameworks, anti-patterns, gotchas
<philosophy>
Philosophy
TypeScript configuration should be strict by default, shared across packages, and forward-compatible. Every project starts with the strictest settings. Shared configs prevent drift. Modern module settings align with bundler-based workflows.
Core principles:
- Strict by default -
strict: trueplusnoUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverride - Share, don't duplicate - Monorepo configs extend a shared base; standalone projects use the same strict options
- Modern module mode -
module: "preserve"+moduleResolution: "bundler"for bundler projects;"node18"/"node20"for Node.js - Path alias parity - Aliases must exist in both tsconfig AND the build tool
<patterns>
Core Patterns
Pattern 1: Shared Strict Config Base
All apps and packages extend a shared strict base. The base config lives in packages/typescript-config/ (monorepo) or is inlined in a standalone project.
// packages/typescript-config/base.json (abbreviated)
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"module": "preserve",
"moduleResolution": "bundler",
"verbatimModuleSyntax": true
}
}
Consumer configs extend the shared base and only add what differs (e.g. paths).
Why good: Single source of truth for strict settings, all packages get the same safety guarantees, consumers only add what differs
See examples/core.md for full base config, consumer configs, and specialized configs (react.json, node.json, library.json).
Pattern 2: Modern Module Settings (TS 5.x+)
verbatimModuleSyntax (TS 5.0+)
Enforces explicit import type for type-only imports. Replaces deprecated importsNotUsedAsValues and preserveValueImports.
// With verbatimModuleSyntax: true
// Good - explicit type import
import type { User } from "./types";
import { createUser } from "./api";
// Bad - type imported as value (errors with verbatimModuleSyntax)
import { User, createUser } from "./api";
module: "preserve" (TS 5.4+)
Preserves import/export syntax as-is. TypeScript only type-checks; the bundler handles module output.
When to use: Bundler-based projects where TypeScript does NOT emit JavaScript (noEmit: true)
When not to use: Node.js packages that emit CJS/ESM directly -- use module: "node18" or "node20" instead
moduleDetection: "force" (TS 5.0+)
Forces all files to be treated as modules, even without import/export. Prevents unexpected global scope pollution.
Pattern 3: ${configDir} Template Variable (TS 5.5+)
Makes shared configs portable. ${configDir} resolves to the directory of the leaf config (the one that extends), not the base.
// packages/typescript-config/base.json
{
"compilerOptions": {
"outDir": "${configDir}/dist",
"rootDir": "${configDir}/src"
},
"include": ["${configDir}/src"]
}
Why good: Consumers don't need to override outDir, rootDir, or include -- paths resolve relative to their own directory
Gotcha: Without ${configDir}, relative paths like "./dist" resolve from the base config's location (packages/typescript-config/), not the consuming package.
Pattern 4: Path Alias Sync
Path aliases must be configured in BOTH tsconfig.json AND your build tool. A mismatch causes either TypeScript errors or build-time import resolution failures.
// tsconfig.json -- paths here for TypeScript type checking
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
// Build tool config -- SAME aliases for bundler resolution
// resolve: { alias: { "@": path.resolve(__dirname, "./src") } }
Gotcha: Forgetting to sync causes "module not found" errors. TypeScript resolves fine but the bundler fails (or vice versa). When adding a new alias, always update BOTH files.
See examples/core.md for full tsconfig + build tool config examples.
Pattern 5: isolatedDeclarations (TS 5.5+)
Requires explicit type annotations on all exports. Enables parallel .d.ts generation by external tools (oxc, swc).
// With isolatedDeclarations: true
// Good - explicit return type on export
export function getUser(id: string): User {
return { id, name: "John" };
}
// Bad - inferred return type (will error)
export function getUser(id: string) {
return { id, name: "John" };
}
When to use: Large monorepos publishing library packages where parallel .d.ts generation speeds up builds
When not to use: Application code that