How to Manage TypeScript Configurations in a Monorepo: A Complete Guide
The Tech Interview Handbook monorepo centralizes TypeScript configurations through a dedicated @tih/tsconfig package that provides reusable base, Next.js, and React library presets, allowing each workspace to extend shared defaults while maintaining project-specific overrides.
Managing TypeScript configurations in a monorepo requires a balance between consistency across workspaces and flexibility for individual projects. The yangshun/tech-interview-handbook repository demonstrates an effective centralized strategy using pnpm workspaces and a dedicated configuration package to manage TypeScript configurations in a monorepo without sacrificing local customization.
Centralized TypeScript Configuration Strategy
The repository employs a single source of truth approach. Instead of duplicating compiler options across dozens of tsconfig.json files, the team maintains a central @tih/tsconfig package under packages/tsconfig/. This package exports three distinct presets tailored to different project types, ensuring that strict mode, module resolution, and JSX handling remain consistent while allowing apps to layer their own path aliases and output directories.
Workspace Layout and Structure
The monorepo organizes code into two primary directories:
apps/– Individual applications (e.g., the portal frontend)packages/– Shared libraries and configuration packages, including thetsconfigpackage
The pnpm Workspace Configuration
The workspace boundaries are defined in pnpm-workspace.yaml at the repository root:
packages:
- 'apps/*'
- 'packages/*'
This configuration instructs pnpm to treat every subdirectory under apps/ and packages/ as an independent package that can depend on other workspaces via the workspace: protocol.
The Shared @tih/tsconfig Package
Located at packages/tsconfig/, this package contains three reusable configuration files. Each preset extends the one above it, creating a layered inheritance model.
Base Configuration (base.json)
The base.json file provides strict, framework-agnostic defaults suitable for any TypeScript project:
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": ["node_modules"]
}
Next.js Configuration (nextjs.json)
The nextjs.json preset extends the base and adds settings optimized for Next.js applications, including JSX preservation and path alias support:
{
"extends": "./base.json",
"display": "Next.js",
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"jsx": "preserve",
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"plugins": [{ "name": "next" }]
},
"include": ["src", "next-env.d.ts"],
"exclude": ["node_modules"]
}
React Library Configuration (react-library.json)
For reusable React component libraries, the react-library.json provides a minimal setup with the React JSX transform:
{
"extends": "./base.json",
"display": "React Library",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2015", "DOM"],
"module": "ESNext",
"target": "ES6"
}
}
Extending Shared Configs in Applications
Individual workspaces consume these presets by extending them in their local tsconfig.json files and overriding specific compilerOptions as needed.
Example: Portal App Configuration
The portal application at apps/portal/tsconfig.json demonstrates how to layer project-specific settings on top of the shared Next.js preset:
{
"exclude": ["node_modules"],
"extends": "@tih/tsconfig/nextjs.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"baseUrl": "./src",
"paths": {
"~/*": ["*"]
}
},
"ts-node": {
"transpileOnly": true,
"compilerOptions": {
"module": "CommonJS"
}
},
"include": ["src", "next-env.d.ts"]
}
Key implementation details:
extendsimports the shared Next.js defaults from@tih/tsconfig/nextjs.json.pathsmaps~/to thesrc/directory, enabling clean absolute imports like~/components/Button.ts-nodeoverrides the module system toCommonJSfor running database seed scripts with Prisma.
Benefits of This Architecture
This centralized approach to managing TypeScript configurations in a monorepo delivers several operational advantages:
- Single source of truth – Updating
packages/tsconfig/base.jsoninstantly propagates to every workspace that extends it, eliminating configuration drift. - Fast iteration – Because
@tih/tsconfigis referenced via theworkspace:protocol (e.g.,"@tih/tsconfig": "workspace:0.0.0"inapps/portal/package.json), pnpm symlinks the files directly. Changes are immediate without npm publishes or version bumps. - Consistent tooling – ESLint, Prettier, and CI pipelines can rely on uniform TypeScript settings for type-checking and linting across all packages.
Adding New Workspaces
To onboard a new package or application while maintaining the centralized configuration strategy:
- Create the directory under
apps/orpackages/. - Add a
tsconfig.jsonthat extends the appropriate shared preset (e.g.,@tih/tsconfig/react-library.jsonfor UI components). - Configure local overrides such as
outDir,rootDir, or custom path aliases incompilerOptions. - Reference the workspace in
pnpm-workspace.yaml(the wildcard patternapps/*andpackages/*automatically includes new folders).
Practical Implementation Examples
Creating a React Component Library
For a new shared UI library at packages/ui, extend the React library preset and enable declaration files for consumers:
{
"extends": "@tih/tsconfig/react-library.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"paths": {
"@ui/*": ["src/*"]
}
},
"include": ["src"]
}
Applications can then import components using the workspace protocol in their package.json:
{
"dependencies": {
"@ui": "workspace:*"
}
}
Configuring Cross-Workspace Path Aliases
To share a common utility package across all workspaces, update the shared base.json to include a global path alias:
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@common/*": ["packages/common/src/*"]
}
},
"exclude": ["node_modules"]
}
Any workspace extending this config can now resolve @common/utils to packages/common/src/utils without additional local configuration.
Running TypeScript Scripts with ts-node
For database seeding or one-off scripts that require TypeScript execution, configure the ts-node section in your app's tsconfig.json to override module settings:
{
"extends": "@tih/tsconfig/nextjs.json",
"compilerOptions": {
"rootDir": "src"
},
"ts-node": {
"transpileOnly": true,
"compilerOptions": {
"module": "CommonJS"
}
}
}
Execute the script using pnpm from the workspace root:
pnpm -C apps/portal ts-node prisma/seed.ts
This ensures the script runs with CommonJS module resolution while the main application uses ESNext.
Summary
- Centralize configurations by creating a dedicated
packages/tsconfigpackage with presets for different project types (base, Next.js, React libraries). - Extend, don't duplicate – Each workspace references shared configs via
"extends": "@tih/tsconfig/nextjs.json"and adds only project-specific overrides. - Leverage workspace protocols – Reference
@tih/tsconfigusing"workspace:0.0.0"indevDependenciesfor instant updates without publishing. - Standardize path aliases – Define common aliases in the shared base config or locally per app to maintain clean import semantics across the monorepo.
Frequently Asked Questions
How do I add a custom path alias to a specific app in the monorepo?
Extend the shared configuration in your app's tsconfig.json and add a paths entry under compilerOptions. For example, to map ~/ to your src/ directory:
{
"extends": "@tih/tsconfig/nextjs.json",
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"~/*": ["*"]
}
}
}
This keeps the shared defaults intact while giving the specific workspace its own import shortcuts.
Why use a workspace package instead of publishing @tih/tsconfig to npm?
Using the workspace: protocol (e.g., "@tih/tsconfig": "workspace:0.0.0") allows changes to the shared configs to propagate immediately to all consuming packages without version bumps, publishing delays, or network requests. Since the monorepo owns all the code, this internal dependency never needs to be published externally, enabling faster iteration and ensuring all workspaces always use the latest compiler settings.
How does the base configuration enforce strict type checking across all projects?
The packages/tsconfig/base.json file sets "strict": true and "noEmit": true at the root level. Because every other preset (Next.js, React library) extends this base file using "extends": "./base.json", these strict settings inherit automatically. Any workspace that then extends @tih/tsconfig/nextjs.json or @tih/tsconfig/react-library.json receives the strict defaults unless explicitly overridden, ensuring consistent type safety standards across the entire monorepo.
Can I override specific compiler options from the shared config?
Yes. The tsconfig.json inheritance model allows child configurations to override any parent setting. For example, if the shared base sets "target": "ES6" but your specific app requires "target": "ES2020", simply declare the new value in your local compilerOptions. The local setting takes precedence while all other unspecified options remain inherited from the shared preset.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →