Skip to main content
ASoc
Tutorial

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.

The ASoc Team9 min read

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

ScopeSwitchWhat it turns offComplains when unnecessary
One line// @ts-expect-error — reasonErrors on the next physical lineYes — TS2578
One line// @ts-ignoreErrors on the next physical lineNo
One file// @ts-nocheckEvery error in that fileNo
DependenciesskipLibCheck: trueErrors inside .d.ts filesNo
Whole projecttypescript.ignoreBuildErrorsThe next build type-check step entirelyNo

@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:

RunErrorsWall time
tsc --noEmit (committed config, skipLibCheck: true)011.35s
tsc --noEmit --skipLibCheck false711.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 toUse
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 buildskipLibCheck: true
Migrate a large JS codebase file by file@ts-nocheck, removed per file
Ship past a type error under a deadlineignoreBuildErrors plus a tsc --noEmit CI step
Keep a file out of the program entirelyStop importing it; exclude alone will not do it

Mistakes and troubleshooting

SymptomCauseFix
@ts-nocheck added, errors still reportedIt is not in the comment block at the top of the fileMove it above every import and statement
A file in exclude is still type-checkedAn included file imports it, so it enters the programRemove the import, or suppress inside the file
Build passes locally, fails in CI on an unrelated fileinclude is **/*.ts — scratch files countDelete 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 useLeave skipLibCheck: true; this is what it is for
CI green but production breaks on typesignoreBuildErrors removed the only checkAdd a tsc --noEmit step to the pipeline
An old @ts-nocheck is hiding new errorsThe directive never reports itself unnecessaryDelete it and fix what surfaces; prefer line scope
@ts-ignore fails lintban-ts-comment in the typescript-eslint presetUse @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.

Keep reading

Tutorial8 min read

A Waitlist Landing Page Is One Server Action and a Honeypot

This storefront's real waitlist form: the Resend Audience API's deprecated field a copy-pasted snippet would miss, and firing one analytics event per successful subscribe, not per render.

Read more
Tutorial9 min read

Web Accessibility Tools: Which Ones Caught This Site's Real Defects

Accessibility 100 on all 8 pages, and two real defects no scanner flagged. Four defects sorted by which tool found them — and why two were structurally invisible.

Read more
Tutorial10 min read

Is Webflow Good for Ecommerce? What the SKU Cap Actually Costs

Webflow's ecommerce plans cap items and charge a transaction fee on top. This storefront's entire commerce stack — entitlements, checkout, webhook — is 843 lines with no cap at all.

Read more