Skip to content

Instantly share code, notes, and snippets.

@TayfunTurgut
Created June 13, 2026 00:25
Show Gist options
  • Select an option

  • Save TayfunTurgut/4559b4e1f2faa376e2f2124e9f85e2ec to your computer and use it in GitHub Desktop.

Select an option

Save TayfunTurgut/4559b4e1f2faa376e2f2124e9f85e2ec to your computer and use it in GitHub Desktop.
TypeScript Config for a NestJS + React pnpm Monorepo

Folder Layout

monorepo/
├── tsconfig.base.json
├── apps/
│   ├── backend/tsconfig.json
│   └── frontend/tsconfig.json
└── packages/
    └── shared/tsconfig.json

Root tsconfig.base.json

Only universal options — no paths or includes:

{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "target": "ES2021"
  }
}

NestJS Backend — apps/backend/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": ["ES2021"],
    "types": ["node"],
    "outDir": "./dist",
    "rootDir": "./src",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "incremental": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

React Frontend — apps/frontend/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2021", "DOM", "DOM.Iterable"],
    "jsx": "react-jsx",
    "types": ["vite/client"],
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

Shared Package — packages/shared/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": ["ES2021"],
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

What Changes and Why

emitDecoratorMetadata + experimentalDecorators

Only NestJS needs these — it relies on runtime reflection for dependency injection. React and plain packages never need them. If a shared package exports NestJS decorators or injectable classes, that package also needs them.

lib

Context Value Reason
Backend ["ES2021"] Node only, no browser APIs
Frontend ["ES2021", "DOM", "DOM.Iterable"] Needs window, document, HTMLElement, etc.
Shared package ["ES2021"] Use the narrowest lib that works

If a shared package has code touching the DOM, it needs "DOM" too — but that's a smell, it probably belongs in the frontend.

module + moduleResolution

Context module moduleResolution Reason
NestJS commonjs node NestJS compiler and Node expect require()
React + Vite ESNext bundler Vite handles module resolution
Shared package commonjs node Match your primary consumer (usually NestJS)

jsx

Only the frontend and any shared UI component package need "react-jsx". Everything else omits it.

noEmit

  • React app: true — Vite does the actual compilation; TypeScript is only type-checking.
  • NestJS + packages: false (default) — tsc actually emits the JavaScript.

composite

Shared packages set this to true to enable TypeScript project references, which lets consuming apps do incremental builds. Apps generally don't need it.

types

Context Value Reason
Backend ["node"] For process, Buffer, __dirname, etc.
Frontend ["vite/client"] For Vite-specific globals like import.meta.env

This keeps type pollution out — your backend won't accidentally use document and your frontend won't accidentally use process.env without import.meta.env.


Tip: Shared Packages Consumed by Both Frontend and Backend

The cleanest approach in a pnpm monorepo is to point the package's main/types in its package.json directly at the source:

{
  "main": "./src/index.ts",
  "types": "./src/index.ts"
}

Let each consuming app's build tool compile it. That way the package tsconfig is just for IDE support and type-checking, and you avoid the CJS-vs-ESM headache entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment