This TypeScript configuration error occurs when your output directory (outDir) is nested inside your source root directory (rootDir), which would cause compiled files to overwrite source files. The fix involves adjusting your tsconfig.json to ensure outDir is outside rootDir or using separate directories for source and compiled code.
The "rootDir should not contain outDir" error is a TypeScript compiler safeguard that prevents you from accidentally configuring your project in a way that would cause compiled JavaScript files to overwrite your TypeScript source files. When you set rootDir in tsconfig.json, you're telling TypeScript: "All my source TypeScript files are somewhere under this directory." When you set outDir, you're saying: "Put all my compiled JavaScript files in this directory." If outDir is inside rootDir, then TypeScript would be writing compiled .js files into the same directory tree as your .ts source files. This creates several problems: 1. **File conflicts**: A file like src/app.ts would compile to src/app.js, potentially overwriting other files 2. **Circular compilation**: TypeScript might re-compile its own output files 3. **Clean separation**: Source and compiled files should be kept separate for clarity and build system efficiency TypeScript prevents this configuration to protect your source code and ensure predictable build outputs.
First, examine your tsconfig.json to understand the current rootDir and outDir settings:
{
"compilerOptions": {
// Problematic configuration example:
"rootDir": "./src",
"outDir": "./src/dist", // ❌ outDir is inside rootDir
// Other common problematic patterns:
// "rootDir": ".",
// "outDir": "./dist", // ❌ If rootDir is current directory
// "rootDir": "./",
// "outDir": "./build" // ❌ Same issue
}
}Identify the exact paths. The error occurs when outDir is a subdirectory of rootDir.
The simplest fix is to change outDir to be outside your source directory:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist", // ✅ Now outside src directory
// Alternative: Use a sibling directory
// "outDir": "./build",
// Alternative: Use absolute path
// "outDir": "/path/to/project/dist"
}
}Common safe patterns:
- "rootDir": "./src", "outDir": "./dist"
- "rootDir": "./lib", "outDir": "./build"
- "rootDir": ".", "outDir": "../dist" (if you want output in parent directory)
Make sure the outDir directory exists or TypeScript will create it.
If you need to keep your current outDir location, make rootDir more specific to exclude it:
{
"compilerOptions": {
// Before: too broad
"rootDir": ".",
"outDir": "./dist",
// After: specify exact source directories
"rootDir": "./src",
"outDir": "./dist",
// Include only source directories
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}
}You can also use multiple rootDirs if you have source code in different locations:
{
"compilerOptions": {
// Don't set rootDir at all if you have multiple source roots
// TypeScript will infer from include patterns
"outDir": "./dist"
},
"include": ["src/**/*", "lib/**/*", "shared/**/*"]
}When rootDir is not specified, TypeScript calculates it from your include patterns.
For monorepos or complex project structures, use TypeScript project references:
// tsconfig.json (root)
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" },
{ "path": "./packages/utils" }
],
"files": [],
"include": []
}
// packages/core/tsconfig.json
{
"compilerOptions": {
"rootDir": ".",
"outDir": "../../dist/core",
"composite": true
},
"include": ["src/**/*"]
}
// packages/ui/tsconfig.json
{
"compilerOptions": {
"rootDir": ".",
"outDir": "../../dist/ui",
"composite": true
},
"include": ["src/**/*"]
}Build with:
npx tsc --buildThis allows each package to have its own rootDir/outDir configuration without conflicts.
After fixing tsconfig.json, clean any previous build artifacts:
# Remove old compiled files
rm -rf dist
rm -rf build
rm -rf out
# Clear TypeScript cache
rm -rf node_modules/.cache
# Rebuild
npx tsc
# Or if using npm scripts
npm run buildIf using an IDE like VS Code, restart the TypeScript server:
1. Press Cmd/Ctrl + Shift + P
2. Type "TypeScript: Restart TS Server"
3. Select the command
Verify the build works:
# Check if files are compiled correctly
ls dist/
# Should see .js files, not .ts filesEnsure your .gitignore properly excludes build directories to prevent committing compiled files:
# .gitignore
dist/
build/
out/
*.js
*.js.map
*.d.ts
# Except if you need declaration files in source control
# !src/**/*.d.ts
node_modules/
.cache/Also update your .eslintignore if using ESLint:
# .eslintignore
dist/
build/
out/
node_modules/For monorepos using workspaces:
# .gitignore for monorepo
packages/*/dist/
packages/*/build/
packages/*/node_modules/### Understanding rootDir Inference
When you don't specify rootDir explicitly, TypeScript infers it from your include patterns. The inferred rootDir is the longest common path prefix of all input files.
Example:
- Files: src/app.ts, src/utils/helper.ts, src/components/Button.tsx
- Inferred rootDir: src/
If you have files in multiple unrelated directories:
- Files: client/src/app.ts, server/src/index.ts, shared/utils.ts
- Inferred rootDir: . (current directory)
This inference can sometimes lead to the "rootDir should not contain outDir" error if your outDir happens to be inside the inferred rootDir.
### outDir Behavior with Multiple rootDirs
TypeScript doesn't support multiple rootDirs in the compilerOptions. However, you can achieve similar results:
1. Using include/exclude patterns:
{
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*", "lib/**/*"],
"exclude": ["dist", "node_modules"]
}2. Using project references (as shown in Step 4)
3. Using path mapping with baseUrl:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@src/*": ["src/*"],
"@lib/*": ["lib/*"]
},
"outDir": "./dist"
}
}### Build Tools Integration
Different build tools handle rootDir/outDir differently:
Webpack with ts-loader:
- ts-loader often doesn't use outDir (it pipes to webpack's output)
- You might not need outDir at all
- Configuration example:
{
"compilerOptions": {
"rootDir": "./src",
// No outDir needed for webpack
"module": "esnext",
"target": "es2015"
}
}Vite:
- Uses esbuild for transpilation
- Typically doesn't use tsc's outDir
- Output directory controlled by vite.config.ts
tsc only (no bundler):
- Requires proper rootDir/outDir configuration
- Use for library publishing or Node.js applications
### Common Pitfalls with Monorepos
In monorepos, watch out for:
1. Nested node_modules: If using workspaces, node_modules might be in parent directory
2. Relative path confusion: ../../dist might point to wrong location
3. Shared tsconfig.json: Base config might not work for all packages
Solution: Use extends with package-specific overrides:
// packages/core/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "../../dist/core"
}
}### Debugging Configuration
To debug rootDir/outDir issues:
# Show compiler settings
npx tsc --showConfig
# Dry run to see what would be compiled
npx tsc --listFiles
# Verbose output
npx tsc --verboseCheck which files TypeScript considers as input and where it plans to output them.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript