Web developmentE-commerce and business

154 type errors our build was throwing away: what we found in them

On 21 September 2026, a type check on our site reported 154 errors, even though the build had been green every time: the check's result was simply thrown away. Inside, we found zeros in link statistics and in the analytics export, sorting that didn't sort, and tests that never ran. Here is how we worked through them, what we changed, and how to check your own project.

On 21 September 2026 we ran npx tsc --noEmit on our own site. This check builds nothing; it only type-checks a TypeScript project. The result: 154 errors.

The number is not the strange part. The strange part is that not one of these errors ever stopped the build. tsconfig.json had strict: true, the strictest checking mode. And right next to it, next.config.mjs carried two lines:

typescript: { ignoreBuildErrors: true },
eslint: { ignoreDuringBuilds: true },

The check ran on every build, and its result was thrown away every time. The build stayed green no matter how many errors there were. The same day, the linter reported 857 issues; the largest groups were 547 about the any type and 300 about unused variables.

Below: what was hiding inside those 154 errors, why defects like these bother nobody, and how to check whether your build actually protects you.

First, decide what to count

154 is everything combined. 65 errors were in one-off scripts and tests that never reach production. The remaining 89 were in code that serves visitors and the admin panel.

So our first step was to move the scripts into a separate tsconfig. An error in a script that was run once to migrate data is worth fixing, but it tells you nothing about the state of the site. By the time we made that split, 132 errors were left, and 59 of them were in the scripts. Count what runs in production; otherwise your priorities blur from day one.

Two defects that showed zeros

The most expensive errors turned out not to be the ones that could have taken a page down. The pages worked. The problems sat where the site shows numbers.

Short link statistics in the admin panel. Every link there has cards and charts: which devices the clicks came from, which countries. All of them showed zero. The server returned the breakdown as a dictionary, roughly like this:

{ desktop: 206, mobile: 33 }

The UI, on the other hand, expected an array and validated its input with Array.isArray. A dictionary fails that check, so the component decided there was no data and rendered a zero. TypeScript said so plainly: an object is not assignable to an array type. The build threw that message away.

After the fix, our link with 252 clicks showed what had been there all along: 206 clicks from desktop, 33 from mobile, and Ukraine as the top country with 199 clicks.

Analytics export. The CSV, Excel, and PDF export buttons worked, the files downloaded, and every cell contained a zero. The data was stored as a "period → number" dictionary, while the export code looked for objects with a .count field in it. A number has no such field, so every value turned into zero. The totals row was even more telling: 0[object Object]. The sum was computed by adding objects instead of numbers to zero, and JavaScript concatenated them into a string.

For the site owner, the consequence is the same in both cases: the report exists, looks normal, and lies. A decision made on such a report is made on zeros.

The other live defects

On the SEO pages tab of the admin panel, sorting by column was silently ignored. The UI sent sortBy and sortOrder parameters, while the server expected orderBy and orderDir. The arrow in the header toggled, and the list stayed in the same order. Someone looking at it is more likely to assume it works as designed than to go hunting for a mismatch in parameter names.

Analytics for the "share" button were recorded with a shift: the platform label got the article URL, and the article URL got the word "button". The function arguments were in the wrong order.

Another small one concerned the confirmation email from the contact form. The template expected a "company" field that exists neither in the form nor in the database, so the "from company …" line never appeared in the email.

Request tracking, which went live on 20 September, had a label leak. For routes under /api, the label was taken from the raw URL, and bots probing addresses like /api/.env or /api/credentials.yml produced 15 of the 30 API labels within the first day. Every new scanner URL is a new series in the metrics system, forever. Now all 404s under /api collapse into a single label.

The strangest find was a CDN module. The site has no CDN, yet an endpoint with no authorization at all served made-up statistics to anyone: "cache hit ratio 81.6%" with January dates. The "cache purge" function purged nothing; it only wrote Cache purged successfully to the log. We deleted the module along with other code nobody called, about 1,300 lines in total.

Traps that haven't gone off yet

Some findings broke nothing only because the relevant code does not currently run.

The project had two request-tracking functions with the same name and different argument order. One module imported the wrong one, so status code 200 ended up in the HTTP method label. As long as that path is not called, there is no harm. The moment it got wired up, the metrics would have been scrambled from the very first request.

The tests turned out to be decoration too: vitest did not run at all, because the jsdom environment package was missing. The test file for the health check route described a version of it that had not existed for a long time. After the fix, the project has 67 tests, and all of them pass.

The rest of the errors fell into the "the type lies about the runtime" category. A field exists in the database but is missing from a hand-written type; the same type is declared twice; a function parameter has no type at all. None of this broke the site. But the live defects above were buried in exactly this noise, and the only way to reach them was to go through all of it.

Why this stays silent

A green build is the strongest "all good" signal there is in development. When errors are ignored, nobody sees them, and every new one drowns among the old ones.

The ignoreBuildErrors flag often appears for a perfectly understandable reason: a fix has to ship urgently, and the build fails on something unrelated. It gets turned on once to deploy quickly and then forgotten.

What matters most is something else: these defects did not crash pages. The admin panel showed zeros, not an error. Zero is a plausible value, because there really might have been no clicks. Nobody reports silent zeros. We already looked at a similar pattern in the story of monitoring that lived only in the docs: metrics declared, dashboards in place, and no data behind them.

How we worked through it

We read every error as the question "what happens here at runtime?", not "how do I silence this?". as any or // @ts-ignore removes the red squiggle in a second and hides the defect for a long time.

These messages got the most attention:

  • "property does not exist" (TS2339): the code reads a field that is not in the data, so it always gets undefined;
  • "argument of the wrong type" (TS2345): often this means swapped arguments, as with the "share" button;
  • "not assignable" (TS2322): the data has a different shape than the UI expects. That is exactly what the zeros in the link statistics looked like.

Every defect we found was verified separately, with data from the database or by running the code. An error disappearing from the editor did not count as proof: you can get that with a wrong fix, too.

What changed on 22 September

Once the code had zero type errors, we turned off the ignoring: ignoreBuildErrors: false. Now a new type error stops the build and never reaches production.

The linter is still disabled in the build for now. It still has hundreds of issues about any, and failing on them now would mean failing every deploy. Unused variables were cleaned up mechanically: from 291 down to 106.

The order of steps matters here. Had we turned off the ignoring first and started fixing afterward, the very first deploy would have stopped.

How to check your own project

First, whether the build pays attention to types at all:

grep -n "ignoreBuildErrors\|ignoreDuringBuilds" next.config.*

If it says true, a green build tells you nothing about the state of the code. Next, count the actual number of errors:

npx tsc --noEmit | grep -c "error TS"

Filter out scripts and tests and look at the application code: that is the site. Start with the three codes that most often mean a real mismatch between data and code:

npx tsc --noEmit | grep -E "TS2339|TS2345|TS2322"

Separately, check whether the tests even start: npx vitest run or npm test. Tests that don't run check nothing, no matter how many of them are in the repository.

Turn on build failures only after the error count reaches zero. Otherwise the next deploy will stop, and someone will flip the flag back.

If you don't have a developer at hand, ask someone to run the first two checks and show you the output. If the config says true and there are dozens of errors, spot-check the numbers in your admin panel: open a statistic whose correct value you know from another source and compare.

Limits of this breakdown

This is one project: our own site. Out of 154 errors, we found seven live defects and a few traps that hadn't gone off yet. In another project, the same errors may hide more, fewer, or nothing at all. The number of type errors doesn't tell you how many breakages are underneath. It only tells you that the build is silent about them.

If you inherited a project from another contractor and nobody knows what actually works and what only looks like it does, this kind of review is part of taking over an abandoned project. If specific breakages have already been spotted, that's fixing a previous contractor's work.

For ourselves, we took away one rule: a check whose result is thrown away is no better than no check at all. It only creates the impression that there is protection.

Tags

Performance

Did you like the article?

Your opinion helps us create better content

Share with friends

Found something useful?

Help others learn about it - share the article on social networks

Thank you for helping us grow

Founder of LIONEX

Vladyslav Chystiakov

Writes about what he builds himself: online stores on OpenCart, applications on Next.js, integrations and site speed. The articles carry measurements and checks a reader can repeat on their own project, not general advice. Commercial development since 2015.

Questions

Frequently asked questions

Answers to common questions on the topic

What does ignoreBuildErrors: true in next.config mean?

Next.js type-checks during the build, but with this flag the result of the check is ignored: the build stays green no matter how many errors there are. That was our situation on 21 September 2026 with strict: true enabled, and tsc --noEmit reported 154 errors the build never noticed.

How can a type error lead to zeros in reports?

When the data has a different shape than the UI expects. In our admin panel, the server returned the click breakdown as a dictionary, while the cards expected an array and checked it with Array.isArray; the dictionary failed the check, and a zero was shown. TypeScript warned about it, but the build threw the message away. After the fix, a link with 252 clicks showed 206 clicks from desktop and 33 from mobile.

Why do defects like these go unnoticed for so long?

Because they don't crash pages. The admin panel shows a plausible zero rather than an error, and nobody reports a zero like that. Meanwhile, a green build creates the impression that everything is fine, and every new type error drowns among the old ones.

Where should I start checking my own project?

Find the ignoreBuildErrors and ignoreDuringBuilds lines in next.config and count the errors with npx tsc --noEmit | grep -c "error TS". Separate one-off scripts and tests from the application code. Read TS2339, TS2345, and TS2322 errors first: they most often mean a real mismatch between data and code. Separately, check whether the tests run at all.

Can I just turn off ignoreBuildErrors right away?

You can, but then the very first deploy will stop on the accumulated errors. We first brought the number of type errors in the application code down to zero, and only then, on 22 September 2026, turned on build failures. The linter is still disabled in the build for now, because it still has hundreds of issues about any.

Get the best articles by email

Subscribe to our newsletter and receive useful tips, insights and news about web development, marketing and business.

We respect your privacy. You can unsubscribe at any time.