VerificationLesson
Type checking
A type error is cheapest to catch before the code runs, but only if a checker reads the file. TypeScript checks only the files its config names, and a project usually has several kinds of code: the app, the scripts around it, the tests. A file that no config names is never checked, and nothing says so.
Know TypeScript's type checking already? Skip to In Terrarium.
Outside the box · General
The concept
The TypeScript compiler, tsc, checks a program: the files a tsconfig.json names with its include and files lists, plus whatever they import, under that file's compiler options. A file no program names and nothing imports is never checked. A glob that matches nothing is not an error while another glob in the list still matches something, so a renamed folder can drop out of a check without a sound. The TSConfig reference documents every option.
A Nuxt app needs more than plain tsc, for two reasons. Its components are .vue files, which hold a template, a script and styles in one file, and tsc can't read them. vue-tsc wraps tsc and turns each .vue file into TypeScript first, so the script and the template's expressions are checked too. And Nuxt code uses names it never imports, such as auto-imported composables and # aliases; Nuxt writes their types and its tsconfig files into .nuxt/. nuxt typecheck writes those files, then runs vue-tsc on them. You don't edit the generated files, since Nuxt rewrites them; you add to them from nuxt.config.ts with the typescript.tsConfig option.
By default Nuxt doesn't check types when you run nuxt dev or nuxt build, for speed; the typescript.typeCheck option turns it on. Code that runs in plain Node, outside the app, needs other settings, such as Node's own types instead of the browser's. The usual answer is a second tsconfig with its own include list, checked by a plain tsc -p run.
Full referenceNuxt docs: TypeScript
In Terrarium
pnpm typecheck in package.json runs two programs, one after the other, and the 's runs it on every change:
15 "typecheck": "nuxt typecheck && tsc -p tsconfig.node.json",
The first is Nuxt's. Its generated tsconfig covers each 's layer, its app/ and its nuxt.config.ts, and shared/, with no help. It doesn't cover a Tenant's , tenant.config.ts; nuxt.config.ts adds one include for the manifests. The same block keeps type checking off in nuxt dev and nuxt build:
37 typescript: {
38 typeCheck: false,
⋯ // …
49 tsConfig: {
50 include: ['../layers/*/tenant.config.*'],
The second program is tsconfig.node.json. It checks the code that runs under Node and tsx, which runs TypeScript without checking it: the scripts, shared/, the tests, and content.config.ts, which Nuxt evaluates at build time rather than compiling into the app. Its include list names each of them:
46 "include": [
47 "scripts/**/*.ts",
48 "shared/**/*.ts",
⋯ // …
57 "content.config.ts"
58 ],
Its options suit Node: Node's types (plus the DOM libs, which type the e2e specs' page.evaluate bodies), and imports that name a sibling file by its .ts path, as tsx runs them. Nuxt registers the #routing and #catalog aliases only for the app; this file repeats them in its paths, pointing at the type files nuxt prepare writes. Both programs keep an incremental build cache under node_modules/.cache/tsc/, which CI restores between runs.
tests/unit/typecheck-coverage.spec.ts reads both configs and fails if the manifest include matches no real manifest, or if content.config.ts leaves the second program.
Why it's built this way
- A second program for Node code.
nuxt typecheckreads only the app, andtsxruns scripts without checking their types, so a type error in a script or test passed the Gate. Terrarium added a Node-side tsconfig with the app's strictness and runs it after Nuxt's check. The cost: two configs, so an alias or option added to one must be mirrored in the other. tsconfig.node.json. - The manifests join Nuxt's program, guarded by a test. A manifest loads at build time without a type check, so an error there passes
pnpm typecheckand surfaces later as a failed build, or never. Terrarium added one include in nuxt.config.ts and a test that fails when it stops matching. The cost: one more test. ADR-0018 (docs/adr/0018-tenant-layers-under-layers-directory.md). - No type check in dev or build. Checking types on every reload and build slows the inner loop. Terrarium keeps Nuxt's default off, says so in its config, and runs the check as a Gate step instead. The cost: a dev server and a build both succeed with type errors, which show only in the editor, a hand-run
pnpm typecheckor the Gate. nuxt.config.ts.
What to watch for
- A green build is not a type check.
pnpm buildandpnpm devnever check types; runpnpm typecheckbefore you push, or wait for the Gate to tell you. - Some unit specs are in neither program. A few specs that test browser code are listed in the
excludeof tsconfig.node.json, and Nuxt's program includes onlytests/nuxt/, so nothing type-checks them; they check their contract when they run. The Nuxt docs show how to add a test folder to Nuxt's program, under "Adding other test directories" in Nuxt's testing guide. - Two files share a name. Nuxt also generates a
.nuxt/tsconfig.node.json, fornuxt.config.tsand the files outside its other contexts. Terrarium's tsconfig.node.json at the root is a different file, and onlypnpm typecheckruns it. - An alias lives in three places. A new
#alias that Node code imports must be added to Nuxt, to tsconfig.node.json and, for tests, to vitest.config.ts.
Try it in your project
For each tsconfig your type check runs, list the files it covers with npx tsc --listFilesOnly -p <tsconfig> (for a Nuxt app's own config, npx vue-tsc --listFilesOnly), and search the lists for your scripts and tests. Any folder in none of them is code nothing checks.
Where next
- LessonMerge gate
- LessonNuxt layers
- LessonTesting
- LessonContent collections
- LessonLinting
Walkthroughs through this Lesson
- WalkthroughAsking an agent for a change
Last updated · Latest source on GitHub ↗