Contents
In brief
Monorepos break on ../../../packages/types chains. The CitizenApp author configures TypeScript path aliases for Astro, React 19, and a shared @shared/types package — readable imports and safer refactors.
What happened
Three approaches to shared types: relative paths (fragile), npm publish (overkill internally), path aliases (Goldilocks). Structure: packages/types/, apps in apps/web (Astro), apps/dashboard (React), root tsconfig.json.
The types package compiles separately with declaration: true, moduleResolution: bundler. package.json exports points to ./dist/index.d.ts — no ambiguity for consumers.
Root config sets baseUrl: "." and paths: { "@shared/*": ["packages/*/src"] }. Project references link workspaces as separate compilation units — faster incremental builds, fewer cycles. Each app extends the root and references packages/types.
Imports become import { User } from '@shared/types' — IDE autocomplete, moves update paths. FastAPI ignores TS aliases; generate Python types via datamodel-code-generator with TS as source of truth.
Why it matters
Without workspace:* in app dependencies, builds fail with "cannot find module" — sometimes silently on CI. Build order: compile packages/types first — explicit in pnpm / Turborepo.
Another gotcha: mismatched moduleResolution locally (nodeNext) vs Vercel (bundler) — paths resolve differently. For modern monorepos, use bundler consistently.
In practice
- Extract shared types to
packages/typeswithexportsand.d.ts. - Configure root
tsconfig.jsonwithbaseUrl,paths, andreferences. - Each app:
extendsplusreferencesto the types package. - In app
package.json:"@shared/types": "workspace:*". - Build the types package first in CI.
- Unify
moduleResolution: bundlereverywhere.
Takeaway
Thirty minutes on correct tsconfig saves hours on imports and refactors. @shared/types instantly signals shared code. Step-by-step configs are in the original on Dev.to.