Skip to main content

Best Practices

Practical TSConfig guidance for Nova that uses small, composable preset chains. Favor explicit wiring and predictable compiler behavior.

Dos and Don'ts

  • What: Use a layered "extends" chain in this order: essentials → strict → role → runtime → framework → tool → your local overrides.
  • Why: The base loads first; the child overrides it. A clear order keeps policy layered and reviewable.
  • What: Choose role-app, role-config, role-scripts, role-source, or role-tests immediately after the strict layer. Reference-only solution files with "files": [] do not compile files and need no role.
  • Why: The role decides whether TypeScript emits source, declarations, or nothing. Runtime and framework presets can then describe the environment without silently changing the build owner.
  • What: Keep them in sync by using "module": "nodenext" with "moduleResolution": "nodenext" for Node ESM, or "module": "esnext" with "moduleResolution": "bundler" for bundlers.
  • Why: Mismatched values make TypeScript and your runtime resolve imports differently, which causes "cannot find module" errors. Pairing keeps resolution consistent.
  • What: Define import aliases in paths without setting the deprecated TypeScript compiler baseUrl option.
  • Why: TypeScript 6 resolves paths entries relative to the TSConfig file when baseUrl is absent.
  • What: Path-related settings like paths, rootDir, outDir, include, and exclude belong in the app's tsconfig.json file, not in Nova presets.
  • Why: TypeScript evaluates these against the file's location. Local wiring avoids resolution mistakes across different repos.
  • What: Set rootDir to the source directory for projects that use tsc or nova utility transpile to emit output.
  • Why: TypeScript 6 no longer lets emitted output layout depend on an inferred source root when that root differs from the TSConfig directory.
  • What: Use types to list the type packages you installed (e.g., @types/node, @types/react), or write [] when the config needs no ambient packages.
  • Why: Makes type sources explicit and under the app's control. Reduces surprises from transitive @types dependencies.
  • What: If your project does not use tsc to build, select role-app and then add the matching framework or tool preset.
  • Why: The app role sets noEmit while framework and tool layers provide their per-file compiler behavior. Each layer has one clear responsibility.
  • What: Do not expect array settings like lib to spread values from the base.
  • Why: In tsconfig, these arrays replace the parent. This is a config limitation, not JS behavior.
  • What: Avoid settings that create unpredictability like incremental, composite, and assumeChangesOnlyAffectDirectDependencies in app tsconfig.json files.
  • Why: These introduce cache drift across environments. Prefer deterministic builds.
  • What: Do not set typeRoots without a strong reason.
  • Why: It limits which @types packages are visible. Defaults usually work best.

Troubleshooting

Find issues by using the command below to print the effective config:

bash
tsc --showConfig --project ./path/to/tsconfig.json