TypeScript Config

The shared compiler options, and the alias everything resolves through.

Three configs, extended by every app and package:

apps/app/tsconfig.json
{
  "extends": "@kreogen/typescript-config/nextjs.json"
}
ConfigFor
base.jsonEverything. Strict, ES2022, NodeNext
nextjs.jsonApps and any package importing React in a Next app
react-library.jsonA React library compiled outside Next

The alias

nextjs.json is where @kreogen/* becomes resolvable:

"paths": {
  "@/*": ["./*"],
  "@kreogen/*": ["../../packages/*"]
}

That single line is why packages need no exports map and no build step: @kreogen/auth/session resolves directly to packages/auth/session.ts.

This path is written relative to an app directory, two levels below the workspace root. It is duplicated in @kreogen/testing, which every test run reads, and the two must agree — a test fails when they drift, because nothing else would tell you. TypeScript reports a missing module and explains nothing about why.

The consequence is worth restating: renaming a file in a package is a breaking API change. There is no indirection between the import specifier and the file on disk.

Strictness worth knowing about

Beyond strict: true, four options are on that many projects leave off:

noUncheckedIndexedAccess. array[0] is T | undefined, and record[key] likewise. This is the one that produces the most friction and prevents the most bugs — a Record<string, number> is typed as total but is not, so an index that misses yields undefined and arithmetic on it silently produces NaN. That exact failure reached a rate limiter's window calculation here before this was enabled.

noUnusedLocals and noUnusedParameters. A dead variable is usually the residue of a half-finished edit. Prefix a genuinely unused parameter with _.

noFallthroughCasesInSwitch. Fallthrough is almost always a missing break, and webhook handlers are full of switches.

isolatedModules. Each file must be transpilable alone, which is what bundlers actually do. It forces import type for type-only imports — annoying until the first time a type import fails to be erased and drags a whole module into a client bundle.

Typecheck

bun run typecheck

Every app and package runs tsc --noEmit. In turbo this depends on ^build, because packages need the generated Prisma client before they will typecheck at all — and @kreogen/database additionally depends on its own build, since it generates that client.

The apps set noEmit: true and declaration: false: Next does the compiling, and tsc is only ever asked whether the code is correct.