跪拜 Guibai
← All articles
Frontend

Five AI-Generated Frontend Bugs That Ship to Production and How to Catch Them

By 前端阿凡 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

AI coding tools lower the barrier to shipping frontend features, but the bugs they introduce are subtle enough to pass local testing and surface only in production — stale data, infinite loops, and silent failures that erode user trust. A line-by-line review checklist for these five patterns prevents the most common escapes.

Summary

Five classes of AI-generated frontend bugs keep surfacing in production: useEffect dependency arrays left empty, CSS Modules class names written as plain strings, TypeScript type safety gutted with `as any`, state updates that read stale closure values, and fetch calls that treat HTTP 4xx/5xx as success. Each pattern stems from the model optimizing for "code that runs" rather than code that is correct under edge conditions.

The fixes are straightforward — complete dependency arrays, functional state updaters, `res.ok` checks, and strict type definitions — but the real defence is a review discipline that treats AI output as a first draft, not a finished feature. ESLint and TypeScript strict mode catch many of these at build time.

Prompting helps: telling the model "no any," "check HTTP status codes," and "consider all reactive dependencies" prevents the worst habits. But state closure bugs and business-logic edge cases still require a human to spot them.

Takeaways
AI-generated useEffect hooks frequently ship with an empty dependency array `[]`, causing components to ignore prop or state changes after the first render.
Mixing Tailwind utility classes with CSS Modules strings (e.g., `className="card-container"`) breaks scoped styles because the class name is never hashed.
TypeScript `as any` assertions spread through a codebase like an infection, stripping type safety from every downstream consumer of the asserted value.
Looping over `setCount(count + i)` reads a stale closure value; React batches state updates, so every iteration sees the same `count` from the render scope.
`fetch()` does not throw on HTTP 4xx or 5xx responses — only network failures trigger `catch`, so missing `res.ok` checks silently treat server errors as success.
Functional state updaters (`setCount(prev => prev + i)`) and `res.ok` guards are the minimal fixes for the two most dangerous patterns.
Adding constraints to prompts — "no any," "check HTTP status codes," "consider all reactive dependencies" — prevents the majority of these bugs before code is generated.
ESLint rules and TypeScript strict mode catch missing dependencies and `any` usage at build time, providing a safety net under AI-generated code.
Conclusions

The model's optimisation target is "code that executes without throwing," which explains why it reaches for `as any` and omits `res.ok` — both are the cheapest path to a green runtime, not a correct one.

These five bug classes are not random; they cluster around the places where JavaScript's runtime semantics diverge from intuitive sequential execution: closure scoping, async batching, and the fetch API's refusal to treat server errors as exceptions.

Prompt engineering acts as a lightweight spec: telling the model "no any" or "handle 4xx" is effectively adding a non-functional requirement that the training distribution underweights, and it measurably reduces defect rates.

The advice to review AI code line-by-line is correct but incomplete — the real skill is knowing which lines to scrutinise, and this list of five patterns is a practical starting heuristic for frontend reviewers.

Concepts & terms
React closure trap (stale closure)
When a function inside a React component captures a variable from its render scope, and that variable is later updated, the function still sees the old value. This causes `setCount(count + 1)` in a loop to use the same initial `count` for every iteration, because React batches state updates and the closure was created in a single render.
CSS Modules
A CSS scoping system where class names written in `.module.css` files are automatically transformed into unique hashed identifiers at build time. Referencing them requires the imported `styles` object (e.g., `styles.cardContainer`); writing a plain string like `"card-container"` bypasses the hash and the style won't apply.
Type assertion (`as any`)
A TypeScript escape hatch that tells the compiler to treat a value as a specific type without any checking. `as any` disables type safety entirely for that variable and everything derived from it, effectively opting out of TypeScript's guarantees for the affected code path.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗