TypeScript Config
The shared compiler options, and the alias everything resolves through.
Three configs, extended by every app and package:
{
"extends": "@kreogen/typescript-config/nextjs.json"
}| Config | For |
|---|---|
base.json | Everything. Strict, ES2022, NodeNext |
nextjs.json | Apps and any package importing React in a Next app |
react-library.json | A 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 typecheckEvery 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.