Next.js ESLint and TypeScript Build Errors
You finally got your AI-generated Next.js app to install all its dependencies. The build starts, compiles for 90 seconds, and then aborts with a single TypeScript error: Type 'string | undefined' is not assignable to type 'string'. Or worse, an ESLint warning about an unused variable escalates to a fatal error and kills the entire production build. You did not write this code. Cursor wrote it. But you are the one staring at a failed deployment at midnight, wondering why a linting nit is blocking your release. This is the Next.js strict build wall, and it is one of the most frustrating failure modes for vibe coders shipping AI-generated applications.
The direct answer: Next.js 15, following the framework's opinionated stance on code quality, enables strict ESLint and TypeScript checking during production builds by default. Any warning becomes an error. Any unused variable, any implicit any, any missing return type on a function can abort next build and leave you with no deployment. For hand-written code reviewed by experienced engineers, this is a reasonable guardrail. For AI-generated code that was never linted or type-checked in a clean environment, it is a deployment blocker.
Why Next.js 15 Treats Warnings as Fatal Errors
The framework's reasoning is sound in principle. Next.js wants production builds to be deterministic and high-quality. If a TypeScript error exists, the type system is telling you something is wrong, and shipping that to production is risky. If an ESLint rule fires, you have violated a code quality standard the team agreed to. Treating these as fatal during next build forces developers to fix them before deploying, which produces a healthier codebase over time.
The problem is that this stance assumes a human review loop. In traditional development, you write code, the IDE underlines errors in real time, you fix them as you go, and by the time you run next build, everything is clean. AI-assisted development breaks this loop. Cursor generates a file, you accept it without reading every line, the IDE might not have time to underline every issue, and the first time anyone sees the TypeScript error is when the production build fails. The strict build wall, designed to enforce quality, becomes a deployment tax on AI-generated code that no human reviewed line by line.
Next.js 15 specifically tightened this behavior compared to 14. In 14, ESLint warnings were warnings, and only ESLint errors aborted the build. In 15, the default next.config.js produced by create-next-app sets eslint.ignoreDuringBuilds to false and treats the entire ESLint run as fatal if any rule fires. The TypeScript compiler was already strict by default since 13.5, but 15 added stricter inference for noUncheckedIndexedAccess and stricter JSX return types. For AI-generated code that frequently uses patterns like const item = items[0] (where item is T | undefined under strict mode), this surfaces as hundreds of errors on a single build.
The framework maintainers are aware of the friction. The Next.js team has acknowledged in RFC discussions that the strict defaults are calibrated for teams with dedicated review cycles, and that solo developers and prototypers may want to opt out. The opt-out mechanism is the two flags in next.config.js, but discovering those flags requires reading documentation that vibe coders rarely read.
The Manual Fix: Editing next.config.js
The standard workaround is to disable strict checking in your next.config.js file. You add two lines:
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
};
module.exports = nextConfig;This tells Next.js to skip ESLint and TypeScript checks during next build, letting the build complete even if there are warnings or type errors. The runtime behavior is unaffected, because these checks are build-time only. Your app runs the same way it would have if the checks had passed.
The problem is that you have to know to do this. A vibe coder who has never touched next.config.js will spend hours reading TypeScript errors and trying to fix them one by one, when the actual fix is a two-line config change. Worse, the LLM that generated the code often does not know to add this config either, because the strict build wall is a Next.js opinion, not a JavaScript language feature, and the LLM's training data mixes Next.js versions with different defaults.
There is a subtler issue. Even if you ask Cursor to add these flags, it will sometimes add them to the wrong config file. Next.js supports next.config.js, next.config.mjs, and next.config.ts. The LLM might write a CommonJS export to a .mjs file, or vice versa, and the build will fail with a different error: SyntaxError: Cannot use import statement outside a module. This is a second-order failure that the vibe coder has no framework for diagnosing, because they were not expecting a config file syntax error in the middle of trying to silence TypeScript errors.
Common Config Patterns
Below is a cheat sheet of valid config patterns for the three Next.js config file types, so you can verify that whatever Deployxa injected (or whatever you wrote manually) is syntactically correct.
// next.config.js (CommonJS)
const nextConfig = {
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
};
module.exports = nextConfig;// next.config.mjs (ESM)
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
};
export default nextConfig;// next.config.ts (TypeScript, Next 15.0+)
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
};
export default nextConfig;All three are functionally equivalent. Deployxa's injector detects which file exists in your repository and patches the appropriate one. If no config file exists, it creates next.config.mjs with the ESM pattern, which is the modern default.
How Deployxa's Build Resilience Injector Works
Deployxa handles this at the platform level. When the ingestion service detects a Next.js project, it checks the next.config.js (or next.config.mjs, or next.config.ts) for the eslint.ignoreDuringBuilds and typescript.ignoreBuildErrors flags. If they are not present, it injects them automatically before the build starts. The injection is logged in the build output, and your source repository is not modified. The change happens in the build context, so your next local npm run dev is unaffected.
This means that the moment you push a Next.js project to Deployxa, the build will not abort on ESLint or TypeScript warnings. The compiler still runs, the types are still checked at runtime where relevant, but the build itself completes. You can fix the underlying type errors at your leisure, in your own branch, without them blocking every deployment.
The injector uses an AST-based patcher, not a regex, so it handles edge cases correctly. If your existing config has nested plugins (like withPWA or withBundleAnalyzer wrappers), the injector adds the flags to the inner config object before the wrapper is applied, which preserves the wrapper's behavior. If your config uses a function form (module.exports = (phase, { defaultConfig }) => ({...})), the injector patches the returned object literal, not the function signature. If your config imports a base config from another file, the injector follows the import and patches the base. These edge cases are where a naive regex patcher would fail silently, and where the AST approach earns its complexity.
What the Injector Does Not Touch
The injector is scoped to ESLint and TypeScript build-time checks only. It does not disable:
- Runtime type validation libraries (Zod, Valibot, Yup). These run at runtime and are not affected by the typescript.ignoreBuildErrors flag.
- Framework-level runtime checks (Next.js's headers() validation, cookies() type narrowing). These are runtime behavior, not build-time checks.
- Custom build scripts in your package.json that call tsc directly. If your build script is tsc && next build, the tsc step will still fail on type errors. The injector only affects the next build step. If you want to skip the standalone tsc step, edit your build script to just next build.
- Storybook builds, test runs, or any other non-Next build steps. The injector is Next-specific.
Step-by-Step: Deploying a Type-Error-Ridden Next.js App
Here is the exact workflow for a typical Cursor-generated app with multiple TypeScript issues.
Step 1: Push your project without fixing type errors
Do not spend time chasing TypeScript errors. Your repository can have implicit anys, unused variables, and missing return types. Deployxa will let the build through.
git add .
git commit -m "ship it, fix types later"
git push origin mainStep 2: Deploy to Deployxa
Connect your repository and click Deploy. The ingestion service runs, detects Next.js, and injects the build resilience flags. You will see log lines like:
[ingest] Detected Next.js project ([email protected])
[ingest] Found next.config.mjs (ESM)
[ingest] Injecting eslint.ignoreDuringBuilds = true
[ingest] Injecting typescript.ignoreBuildErrors = true
[ingest] Build resilience configuredStep 3: Watch the build complete
The build runs, the compiler emits warnings instead of errors, and the build completes. Your app is live. You can now access it at your Deployxa URL and verify that it works end to end.
Step 4: Fix type errors in a follow-up branch
Now that the app is live, you can take your time fixing the underlying TypeScript issues. Create a branch, fix the types, run tsc --noEmit locally to verify, and merge when ready. The next deployment will pick up the fixes, and the build resilience flags will still be there as a safety net.
git checkout -b fix/typescript-errors
# fix the types...
npx tsc --noEmit
git commit -am "fix typescript errors"
git push origin fix/typescript-errorsStep 5: Use deployxa doctor for ongoing health checks
Once your app is live, run deployxa doctor periodically to check its health. The 14-point readiness engine verifies SSL, DNS, environment variables, health endpoints, and container status, giving you a plain-English grade from A to F.
Common Pitfalls
Three pitfalls appear regularly. First, runtime type mismatches that the build did not catch. Disabling typescript.ignoreBuildErrors lets the build through, but it does not make wrong code right. If your code passes a string to a function that expects a number, the build will succeed and the runtime will throw TypeError: Cannot read properties of undefined or similar. The injector is not a substitute for runtime testing; it is a build-time bypass. Second, ESLint rules that have runtime side effects. The next/no-html-link-for-pages rule, for example, flags in favor of because the latter does client-side routing. Disabling ESLint during builds lets the through, which means slower page transitions in production. The build succeeds, but the UX is degraded. Third, the injector only affects next build, not next dev. If you run npm run dev locally, you will still see all the TypeScript errors in your terminal, which can be confusing if you assumed the injector suppressed them everywhere. The injector is build-only by design, because local dev is where you want the feedback loop.
Troubleshooting: When the Build Still Fails
If the build still fails after the injector ran, the failure is not in the ESLint or TypeScript layer. Below are common follow-on failures and their meanings.
Failed to compile.
./src/app/page.tsx
Module not found: Can't resolve '@/components/Header'This is a path alias resolution failure, not a type error. Check your tsconfig.json paths configuration. The injector does not touch path aliases.
> next build
Error: connect ECONNREFUSED 127.0.0.1:3000This is the localhost trap, not a TypeScript error. The injector does not address it; the localhost rewriter does. See the companion article.
TypeError: next.config.mjs: Cannot use import statement outside a moduleThe config file syntax is wrong for its extension. The injector should have produced valid syntax, but if you manually edited the file afterward and broke it, this is the error. Restore from git history or delete the file and redeploy to let the injector recreate it.
Error: ESLint configuration in .eslintrc.json is invalidYour .eslintrc.json itself is malformed. The injector's ignoreDuringBuilds flag silences ESLint execution during builds, but it does not validate your ESLint config. Fix the JSON syntax error in .eslintrc.json first.
The Trade-Off: Strictness vs Shipping Speed
Disabling strict type checking during builds is a trade-off. You gain the ability to ship quickly, which matters when you are iterating on an AI-generated prototype. You lose the safety net that catches type errors before they reach production. The right balance depends on your stage. For a vibe coder shipping a landing page to validate an idea, shipping speed wins. For a team maintaining a production app with paying users, type safety wins. Deployxa defaults to shipping speed because that matches the vibe coder use case, but you can re-enable strict checking at any time by editing your next.config.js and pushing again.
The key insight is that this should be your choice, not a framework default imposed on you. Deployxa gives you the choice by making the relaxed configuration the default at the platform level, while still allowing you to opt back into strictness when you are ready.
Cost Comparison: Strict Builds vs Bypassed Builds
There is also a cost dimension. Strict builds fail often, and each failed build on Vercel consumes build minutes. A vibe coder iterating on an AI-generated app can burn through 20 to 40 build minutes in a single afternoon of failed deploys. On Vercel Hobby (6,000 minutes per month), that is a noticeable chunk of the monthly budget. On Vercel Pro ($20/month), it is $20 plus potential overages. On Deployxa, build minutes are not metered separately; the paid tier is $9 per month flat for 15 apps, with no per-build-minute billing. The injector's value is not just developer time saved; it is also build minutes saved, which translates directly to dollars on platforms that meter builds.
| Build behavior | Vercel Hobby | Vercel Pro | Deployxa Free | Deployxa Paid |
|---|---|---|---|---|
| Strict build (TS errors abort) | Counts against 6,000 min/mo | Counts against 6,000 min/mo + $40 overage | Not metered | Not metered |
| Bypassed build (TS errors warn) | Same cost, but succeeds on first try | Same cost, but succeeds on first try | Not metered | Not metered |
| Iterating on AI code (10 projects/mo) | Likely exhausts 6,000 min | $20 + $10-30 overages | Included in 3-app limit | $9 flat |
How This Fits the Broader Auto-Healing Story
The build resilience injector is one piece of Deployxa's auto-healing stack. The AutoRepairService handles missing npm packages. The localhost rewriter handles hardcoded URLs. The build resilience injector handles ESLint and TypeScript strictness. The pre-flight scanner handles missing environment variables. Together, these systems form a safety net that catches the most common AI coding mistakes and fixes them at the platform level, so you never have to debug them manually.
Each of these systems is bounded and transparent. They log what they changed, they do not modify your source repository, and they only handle the specific failure modes they were designed for. They are not a magic black box that rewrites your code; they are targeted interventions that eliminate the boring, repetitive failure modes that kill vibe coder momentum.
When Deployxa Is Not the Right Choice
There are scenarios where bypassing strict builds is the wrong call, and Deployxa's defaults will not serve you well. If you are shipping a fintech app where type mismatches can cause real money to move incorrectly, you want strict TypeScript enforced at build time, and you should remove the injector's flags manually after the first deploy. If you are working in a regulated industry with code review requirements, the injector's silent bypass may violate your team's review policy, and you should document the bypass explicitly or disable it. If you are building a library that other developers consume, you want strict types so your published .d.ts files are accurate, and the injector's bypass will ship inaccurate type declarations. In each of these cases, the fix is the same: edit next.config.js to remove the two flags, push, and Deployxa will respect your explicit configuration without re-injecting. The injector only adds the flags if they are absent; it never overrides an explicit false.
Conclusion: Ship First, Fix Types Later
The Next.js strict build wall is a reasonable framework default that becomes unreasonable when applied to AI-generated code no human reviewed line by line. Deployxa's build resilience injector removes that wall at the platform level, so you can ship your app today and fix the types tomorrow. Stop chasing TypeScript errors at midnight and start shipping.
Ready to ship without the type tax? Drag your project to Deployxa Drop for an instant live preview, or install the CLI with npm i -g @deployxa/cli and deploy from your terminal. For more on how Deployxa handles AI coding patterns, see our free developer tools and read about the localhost trap in our companion article.