TypeScript Ignore Type Check: Four Scopes, Measured in One Repository
@ts-nocheck must be line 1, tsconfig exclude does not stop checking, skipLibCheck saved 0.2s, and ignoreBuildErrors deleted the only type gate this CI had.
To ignore type checking for a whole file, put // @ts-nocheck in the comment block at the very top. To skip other people's declaration files, set skipLibCheck: true. To stop next build failing on type errors, set typescript.ignoreBuildErrors. They are three different switches at three different scopes, and only the first is reversible by a code reviewer.
That is the distinction most answers blur. "Ignore type checking" is asked at the line, the file and the project level, and the wrong scope either does nothing or silently removes the only check you had. Every output below was measured in this repository — TypeScript 5.9.3, Next.js 16.2.9, strict: true, 220 .ts/.tsx files — not quoted from documentation. For suppressing a single line, which is a different question with a different answer, see TypeScript ignore next line.
The four scopes
| Scope | Switch | What it turns off | Complains when unnecessary |
|---|---|---|---|
| One line | // @ts-expect-error — reason | Errors on the next physical line | Yes — TS2578 |
| One line | // @ts-ignore | Errors on the next physical line | No |
| One file | // @ts-nocheck | Every error in that file | No |
| Dependencies | skipLibCheck: true | Errors inside .d.ts files | No |
| Whole project | typescript.ignoreBuildErrors | The next build type-check step entirely | No |
@ts-nocheck: whole file, and it must be at the top
Two files, identical bodies, one with the directive. The body has two genuine errors under strict:
type Slot = { slug: string; version: string };
declare const s: Slot;
const n: number = s.slug; // TS2322
delete s.version; // TS2790
export const out = n;
Run through the repo's own tsc --noEmit, the plain file reported both:
tmp-tscheck/a-plain.ts(3,7): error TS2322: Type 'string' is not assignable to type 'number'.
tmp-tscheck/a-plain.ts(4,8): error TS2790: The operand of a 'delete' operator must be optional.
With // @ts-nocheck as line 1, the same file reported nothing. Whole-file suppression works exactly as advertised.
Now the trap. The directive only counts if it is in the comment block before any statement. This file still errors:
import { out } from "./b-nocheck";
// @ts-nocheck <- too late, this does nothing
const n: number = "not a number";
tmp-tscheck/c-nocheck-late.ts(3,7): error TS2322: Type 'string' is not assignable to type 'number'.
One import above the directive and the file is checked as if the comment were not there. There is no warning that the directive was ignored — the error you were suppressing simply stays, and the usual conclusion is "@ts-nocheck doesn't work."
The second trap is quieter. A file with // @ts-nocheck and no errors produced no complaint at all. Compare that with @ts-expect-error, which raises TS2578: Unused '@ts-expect-error' directive the moment its line stops erroring. A @ts-nocheck cannot report itself unnecessary, so it survives every refactor that would have made it removable, and from then on it hides the next real error in that file. That asymmetry is the whole argument for keeping suppressions at line scope wherever you can.
exclude does not mean "don't check"
The obvious next idea is to drop the file out of tsconfig.json. It does not work the way it reads. With the file listed in exclude, and one included file importing it:
// tsconfig.json
"exclude": ["node_modules", "tmp-tscheck/a-plain.ts"]
tmp-tscheck/a-plain.ts(3,7): error TS2322: Type 'string' is not assignable to type 'number'.
tmp-tscheck/a-plain.ts(4,8): error TS2790: The operand of a 'delete' operator must be optional.
Both errors, still reported. include and exclude decide which files are root files — where the compiler starts — not which files get checked. Anything reachable through an import from a root file is pulled into the program regardless. exclude only silences a file that nothing imports, which is rarely the file causing trouble.
skipLibCheck: not the performance knob it is sold as
This repo ships skipLibCheck: true, and the usual justification is build speed. Measured here, that justification is wrong. Same tree, same machine, back to back:
| Run | Errors | Wall time |
|---|---|---|
tsc --noEmit (committed config, skipLibCheck: true) | 0 | 11.35s |
tsc --noEmit --skipLibCheck false | 7 | 11.57s |
Two tenths of a second. Checking every declaration file in node_modules cost essentially nothing. What it produced was seven errors, and all seven are in other people's packages:
node_modules/@mdx-js/loader/index.d.cts(18,36): error TS2307: Cannot find module 'webpack'...
node_modules/@mdx-js/loader/lib/index.d.ts(29,36): error TS2307: Cannot find module 'webpack'...
node_modules/@next/mdx/index.d.ts(3,47): error TS2307: Cannot find module 'webpack'...
node_modules/@types/mdx/types.d.ts(23,38): error TS2503: Cannot find namespace 'JSX'.
Three are Cannot find module 'webpack', raised because the MDX loader's types reference webpack and this project builds with Turbopack, so webpack's types are not installed and never will be. Four are Cannot find namespace 'JSX' inside @types/mdx. Zero are in application code.
That is the honest case for skipLibCheck: it is a blast shield against dependencies whose types do not compile in your configuration, and it buys roughly no time. Which also means turning it off to "be stricter" makes your build fail on packages you cannot fix, while catching nothing in the code you wrote.
ignoreBuildErrors: the one that deletes the check
The project-level switch is the dangerous one, and it is worth seeing both halves. A loose file with a type error, imported by nothing, sitting in the repo. First with the config as committed:
✓ Compiled successfully in 38.6s
Running TypeScript ...
Failed to type check.
./tmp-tscheck/a-plain.ts:3:7
Type error: Type 'string' is not assignable to type 'number'.
Next.js build worker exited with code: 1 and signal: null
Note what failed: a file no route imports. next build type-checks everything matching tsconfig.json's include, which here is **/*.ts and **/*.tsx. A scratch file left in the tree fails the production build.
Then the same tree with one line added:
// next.config.ts
const nextConfig: NextConfig = {
typescript: { ignoreBuildErrors: true },
// ...
};
The build succeeded and prerendered all 111 product pages. The type error was still there, unchanged, in the shipped tree. Next.js's own documentation is blunt about why: it "does not run TypeScript and suppress errors, it bypasses the check entirely."
Why that is worse here than it sounds
The documentation's own advice is to "be sure you are running type checks as part of your build or deploy process." After checkout and Node setup, this repo's CI job runs five commands:
# .github/workflows/ci.yml
- run: npm ci
- run: npm run lint
- run: npm run format:check
- run: npm run test --if-present
- run: npm run build
There is no tsc --noEmit step. next build is the type gate — the only one. Setting ignoreBuildErrors: true here would not relax type checking in CI, it would remove it, and every subsequent pull request would go green with type errors in it. If you reach for this flag, add the separate tsc --noEmit step in the same commit. The flag is a deadline tool; the missing step is what makes it survivable.
Pick by scope
| You want to | Use |
|---|---|
| Bypass one deliberate violation | @ts-expect-error — reason |
| Silence a generated or vendored file you will not fix | @ts-nocheck at the top of it |
Stop a dependency's broken .d.ts failing your build | skipLibCheck: true |
| Migrate a large JS codebase file by file | @ts-nocheck, removed per file |
| Ship past a type error under a deadline | ignoreBuildErrors plus a tsc --noEmit CI step |
| Keep a file out of the program entirely | Stop importing it; exclude alone will not do it |
Mistakes and troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
@ts-nocheck added, errors still reported | It is not in the comment block at the top of the file | Move it above every import and statement |
A file in exclude is still type-checked | An included file imports it, so it enters the program | Remove the import, or suppress inside the file |
| Build passes locally, fails in CI on an unrelated file | include is **/*.ts — scratch files count | Delete the scratch file, or keep it out of the glob |
skipLibCheck: false fails on Cannot find module 'webpack' | A dependency's types reference a bundler you do not use | Leave skipLibCheck: true; this is what it is for |
| CI green but production breaks on types | ignoreBuildErrors removed the only check | Add a tsc --noEmit step to the pipeline |
An old @ts-nocheck is hiding new errors | The directive never reports itself unnecessary | Delete it and fix what surfaces; prefer line scope |
@ts-ignore fails lint | ban-ts-comment in the typescript-eslint preset | Use @ts-expect-error with a description |
FAQ
How do I disable type checking for one file?
Put // @ts-nocheck in the comment block at the very top, before any import. It suppresses every error in that file, and it will never tell you when it has become unnecessary.
Does skipLibCheck make compilation faster?
Barely. In this repo it was the difference between 11.57s and 11.35s. Its real job is to stop a dependency's un-compilable declaration files from failing your build — here, seven errors from packages referencing webpack and a missing JSX namespace.
Is ignoreBuildErrors ever acceptable?
Only alongside a real type check elsewhere. It does not suppress errors, it skips the step, so unless a separate tsc --noEmit runs in CI it removes type safety from the pipeline rather than loosening it.
Why does my build fail on a file no page imports?
Because next build checks every file matching tsconfig.json's include, not just the route import graph. A **/*.ts glob covers scratch files, one-off scripts and anything else left in the tree.
Templates in this post
Starting from a template that already type-checks clean under strict is the cheapest way to never need any of these switches. ASoc Brief, ASoc Byte and ASoc Canvas ship as typed Next.js code you own.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For the neighbouring pieces, see TypeScript ignore next line, Next.js with TypeScript and TypeScript vs TypeScript SWC.
