TypeScript Ignore Next Line: Why This Repo Has One @ts-expect-error and Zero @ts-ignore
Suppress a TypeScript error on one line the safe way. Measured TS2578, TS2790 and ban-ts-comment output, the multi-line trap, and a cast that avoids the directive.
To ignore the next line in TypeScript, put // @ts-expect-error — reason directly above it. It suppresses the compiler error on that single line, and it fails the build if the line stops erroring. // @ts-ignore does the same silently and never complains. This codebase has zero @ts-ignore and exactly one @ts-expect-error, and the reasons are measured below.
The short answer
// @ts-ignore | // @ts-expect-error | |
|---|---|---|
| Suppresses the error on the next line | Yes | Yes |
| Complains when the next line has no error | No | Yes — TS2578: Unused '@ts-expect-error' directive |
| Passes this repo's lint config | No | Only with a description of 3+ characters |
| Covers a multi-line statement | Only its first line | Only its first line |
| Right for | Almost nothing | A deliberate, documented type violation |
The one place this repo uses it is a webhook test, src/lib/__tests__/webhook.test.ts, line 160:
const fixture = structuredClone(
orderCreatedFixture,
) as typeof orderCreatedFixture;
// @ts-expect-error — test fixture mutation for the unattached-order path
delete fixture.meta.custom_data.user_id;
The fixture is a LemonSqueezy order_created payload. The test needs a purchase that arrives with no custom_data.user_id, so the webhook handler stores it as unattached and fires the alert callback. user_id is a required key in the JSON's inferred type, so TypeScript refuses to delete it.
What each directive actually does
Everything below was run in this repository against TypeScript 5.9.3 and typescript-eslint 8.62.0, in a scratch file with strict: true from the repo's tsconfig.json.
Deleting a required property, with no directive:
error TS2790: The operand of a 'delete' operator must be optional.
Now the four cases that matter:
type Fixture = { meta: { custom_data: { user_id: string } } };
declare const f: Fixture;
// @ts-ignore
delete f.meta.custom_data.user_id; // suppressed
// @ts-expect-error — mutation
delete f.meta.custom_data.user_id; // suppressed
// @ts-expect-error — nothing wrong here, directive is stale
const ok: number = 1; // TS2578 on the directive
// @ts-expect-error — only the NEXT line is covered
const multi = {
a: 1,
b: "x" as number, // TS2352 — NOT suppressed
};
tsc reported exactly two errors: TS2578: Unused '@ts-expect-error' directive on the stale one, and TS2352 on the object literal's third line. The two real violations were silenced; the stale directive and the second-line error were not.
That is the whole difference, and it is the reason to prefer one over the other. A @ts-ignore never reports itself unused, so when someone later fixes the types, the comment stays behind and hides the next genuine error on that line. A @ts-expect-error turns red the moment it stops being true, so cleanup is forced rather than remembered.
"Next line" means the next physical line
The directive attaches to the line after the comment, not the statement. In the multi example above, the comment sat over const multi = { and the error was on b: "x" as number, two lines lower. It was not suppressed.
Two ways to deal with that:
- Move the directive to sit directly above the offending line inside the literal.
- Better, restructure so the violation is on a line of its own, such as assigning the bad value to a named constant first.
JSX needs a different comment shape
Inside JSX children a // comment is text, not a comment. Use a braced block comment:
export const X = () => (
<div>
{/* @ts-expect-error — foo is not a div prop */}
<div foo="1" />
</div>
);
This compiled clean in the same scratch run, with the invalid foo prop suppressed and no TS2578.
What this repo's lint config enforces
eslint.config.mjs extends eslint-config-next's typescript preset, which brings in typescript-eslint's ban-ts-comment rule. Running it over a scratch file produced:
4:1 error Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free @typescript-eslint/ban-ts-comment
So @ts-ignore fails npm run lint outright, and CI runs lint on every push. A bare @ts-expect-error fails too:
2:1 error Include a description after the "@ts-expect-error" directive to explain why the @ts-expect-error is necessary. The description must be 3 characters or longer @typescript-eslint/ban-ts-comment
A one-character description written as // @ts-expect-error: x also passed in the same run, so the rule checks that a description exists, not that it is any good. The review habit that closes the gap is the one in webhook.test.ts: say what you are bypassing and why, so the next reader can judge whether it is still necessary.
Before you suppress, can you cast?
Most suppressions are really "I know something the types don't." A suppression says "ignore everything on this line"; a cast says one specific thing. Here is the same fixture mutation with no directive, checked with tsc --noEmit and ESLint in the same scratch run:
delete (
fixture.meta.custom_data as Partial<typeof fixture.meta.custom_data>
).user_id;
Both passed with no output. The cast is narrower, because it only widens custom_data to optional keys and leaves the rest of that line checked. The repo keeps the directive in the test anyway, because the comment documents intent in one line. Either is defensible; what is not defensible is a suppression with no reason attached.
The same logic applies to eslint-disable-next-line, the other next-line escape hatch. This codebase has one, in src/components/molecules/MarqueeRow.tsx line 15, scoped to a single named rule (@next/next/no-img-element) rather than disabling lint wholesale. Name the rule you are silencing. A bare eslint-disable-next-line is the @ts-ignore of linting.
Next.js and type checking
next build type-checks every file, so a suppression in application code is still a suppression the production build respects. That matters for the other direction too: removing a stale @ts-expect-error is not optional housekeeping, because the build fails with TS2578 until you do. The repo's CI runs lint, format:check, test and build on every push, so a stale directive cannot reach main unnoticed.
Mistakes and troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
TS2578: Unused '@ts-expect-error' directive | The error it covered is gone, or the directive is above the wrong line | Delete the directive, or move it to the line that actually errors |
| Directive added, error still reported | The error is on a later line of a multi-line statement | Put the directive directly above the offending line |
ban-ts-comment on @ts-ignore | The preset bans it | Use @ts-expect-error with a description |
ban-ts-comment on @ts-expect-error | No description, or under 3 characters | Add a real reason after the directive |
| Directive in JSX shows as literal text on the page | // is not a comment inside JSX children | Use {/* @ts-expect-error — reason */} |
| Suppressed error resurfaces as a runtime crash | The directive hid a real type error rather than a deliberate violation | Fix the type, or narrow with a cast and a guard |
Frequently asked questions
What is the difference between @ts-ignore and @ts-expect-error?
Both suppress the compiler error on the next line. @ts-expect-error additionally raises TS2578 when that line has no error, so the comment cannot outlive the problem it covered.
Can I ignore a whole file?
// @ts-nocheck at the top of a file disables checking for the file. It is rarely right in a project that runs next build, since the check you are skipping is the one that protects production.
Why does the directive not silence an error two lines down? It attaches to the next physical line only. Put it directly above the line that errors, or restructure so the violation sits on its own line.
Should I use as unknown as T instead?
When you can name the type you actually have, a targeted cast such as the Partial<...> above is narrower than a double cast through unknown. A double cast tells the compiler to stop looking at that expression entirely, which is close to what @ts-ignore does.
Templates in this post
ASoc Quill is a lightweight publication template with a featured-post hero, a stories feed and Minimal, Grid and Slider layouts — a content-driven site where typed post metadata earns its keep. ASoc Rally markets an AI CRM with a pipeline-overview hero and a three-tier pricing table, the kind of billing-adjacent surface that sits next to webhook handlers like the one tested above. ASoc Rank is an SEO-audit SaaS site with a free, premium and enterprise pricing table, where plan data is exactly the shape worth keeping strictly typed.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
