Code généré par IA : 7 patterns que je refuse en review
Publié le
Développeur React & Next.js freelance
Le volume de code généré par IA a explosé. Les pull requests sont plus grosses, plus fréquentes, et souvent produites en quelques minutes. La review devient le vrai goulot d'étranglement : le volume augmente, mais la capacité de vérification reste la même.
Cet article présente les 7 patterns problématiques les plus récurrents dans le code généré par IA, avec des exemples avant/après en React et TypeScript.
Dans cet article :
- Pourquoi la review de code IA nécessite une vigilance spécifique
- Les 7 patterns problématiques les plus fréquents, avec code avant/après
- Une checklist de review résumée
- Les erreurs à éviter en tant que reviewer
Le contexte : plus de code, plus de vigilance
Le code généré par IA doit être traité comme du code écrit par un développeur junior talentueux mais sans connaissance du projet. Il compile, il fonctionne dans le cas nominal, mais il ignore le contexte : conventions, architecture, edge cases, sécurité.
Avec un bon contexte (fichier CLAUDE.md, rules, conventions explicites), la qualité monte significativement. Mais même avec un setup bien configuré, une part d'aléatoire subsiste : la fenêtre de contexte a ses limites, l'IA interprète différemment selon la formulation, et la configuration varie d'un développeur à l'autre. La review reste le filet de sécurité indispensable.
1. any et typage lâche
Le problème le plus fréquent. L'IA génère des any quand elle ne comprend pas le type attendu, ou utilise des assertions as pour forcer le typage.
// ❌ any et assertions non vérifiées
'use server'
const updateProfile = async (formData: FormData) => {
const data = Object.fromEntries(formData) as any
const user = await db.user.update({
where: { id: data.userId },
data: { name: data.name, email: data.email },
})
return user
}Trois problèmes : as any supprime la vérification TypeScript, les données du formulaire ne sont pas validées, et la fonction ne type pas son retour.
// ✅ Validation Zod + types explicites
'use server'
import { z } from 'zod'
const updateProfileSchema = z.object({
userId: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
})
const updateProfile = async (
formData: FormData
): Promise<Result<User>> => {
const parsed = updateProfileSchema.safeParse(
Object.fromEntries(formData)
)
if (!parsed.success) {
return { success: false, error: 'validation' }
}
const user = await db.user.update({
where: { id: parsed.data.userId },
data: { name: parsed.data.name, email: parsed.data.email },
})
return { success: true, data: user }
}Signal d'alerte en review : chercher any, as unknown as, et @ts-ignore. Si l'IA en a besoin, c'est que le type manque ou que la structure est incorrecte.
Pour approfondir la validation aux frontières : Zod aux frontières : valider les données au-delà des formulaires.
2. Composants monolithiques
L'IA génère naturellement des composants de 200+ lignes qui mélangent fetch, logique métier, gestion d'état et rendu. Le résultat : un fichier impossible à tester et à maintenir.
// ❌ Composant monolithique généré par IA
'use client'
import { useState, useEffect } from 'react'
const OrderList = () => {
const [orders, setOrders] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [filter, setFilter] = useState('all')
const [total, setTotal] = useState(0)
useEffect(() => {
fetch('/api/orders')
.then(res => res.json())
.then(data => {
setOrders(data)
setLoading(false)
})
}, [])
useEffect(() => {
const filtered = orders.filter(o =>
filter === 'all' ? true : o.status === filter
)
setTotal(filtered.reduce((sum, o) => sum + o.total, 0))
}, [orders, filter])
if (loading) return <p>Chargement...</p>
return (
<div>
<select onChange={e => setFilter(e.target.value)}>
<option value="all">Toutes</option>
<option value="pending">En attente</option>
<option value="completed">Terminées</option>
</select>
<ul>
{orders
.filter(o => (filter === 'all' ? true : o.status === filter))
.map(order => (
<li key={order.id}>
{order.product} - {order.total} EUR - {order.status}
</li>
))}
</ul>
<p>Total : {total} EUR</p>
</div>
)
}Problèmes : any, fetch dans un useEffect, état dérivé synchronisé dans un second useEffect, logique de filtrage dupliquée, pas de gestion d'erreurs, "use client" inutile pour l'affichage des données.
// ✅ Server Component pour les données
import { db } from '@/lib/db'
import { OrderFilters } from '@/components/order-filters'
const OrdersPage = async () => {
const orders = await db.order.findMany({
include: { product: true },
})
return <OrderFilters orders={orders} />
}
export default OrdersPage// ✅ Client Component uniquement pour l'interactivité
'use client'
import { useState } from 'react'
import { type Order } from '@/types/order'
const OrderFilters = ({ orders }: { orders: Order[] }) => {
const [filter, setFilter] = useState('all')
const filtered = orders.filter(o =>
filter === 'all' ? true : o.status === filter
)
const total = filtered.reduce((sum, o) => sum + o.total, 0)
return (
<div>
<select
value={filter}
onChange={e => setFilter(e.target.value)}
>
<option value="all">Toutes</option>
<option value="pending">En attente</option>
<option value="completed">Terminées</option>
</select>
<ul>
{filtered.map(order => (
<li key={order.id}>
{order.product.name} - {order.total} EUR -{' '}
{order.status}
</li>
))}
</ul>
<p>Total : {total} EUR</p>
</div>
)
}
export { OrderFilters }Le composant monolithique est devenu deux fichiers : un Server Component qui récupère les données et un Client Component minimal pour le filtre. L'état dérivé (filtered, total) est calculé pendant le render, pas dans un useEffect.
Signal d'alerte en review : un composant de plus de 100 lignes qui mélange useState, useEffect et JSX. Si la logique métier est dans le composant, elle n'est pas testable indépendamment.
3. useEffect comme orchestrateur
L'IA utilise useEffect comme solution par défaut pour tout : fetch de données, synchronisation d'état, réaction aux changements de props. C'est l'anti-pattern le plus documenté de React 19.
Les cas les plus fréquents dans le code généré par IA :
| Pattern IA | Alternative |
|---|---|
useEffect + fetch | Server Component ou use() |
useEffect pour état dérivé | Calcul pendant le render |
useEffect pour réagir à un événement | Handler d'événement |
useEffect pour réinitialiser le state | Prop key |
Cet article ne détaille pas chaque cas. Pour les exemples avant/après de chaque situation : Anti-patterns React en 2026 : 6 erreurs courantes.
Signal d'alerte en review : plus de 2 useEffect dans un composant. Chaque useEffect doit avoir une raison claire (synchronisation avec un système externe). Si le useEffect appelle un setState, c'est probablement un anti-pattern.
4. "use client" par défaut
L'IA ajoute "use client" en haut de chaque fichier. C'est logique : la majorité des exemples de son corpus d'entraînement précèdent les Server Components. Le résultat : un bundle JavaScript envoyé au navigateur qui pourrait être du HTML statique.
// ❌ "use client" sans raison
'use client'
const AboutPage = () => {
return (
<main>
<h1>A propos</h1>
<p>Notre entreprise a été fondée en 2020.</p>
<ul>
<li>42 clients accompagnés</li>
<li>15 pays</li>
</ul>
</main>
)
}
export default AboutPageCe composant n'utilise aucun hook, aucun événement, aucune API navigateur. "use client" envoie ce code en JavaScript au navigateur alors qu'il pourrait être rendu en HTML côté serveur.
// ✅ Server Component par défaut
const AboutPage = () => {
return (
<main>
<h1>A propos</h1>
<p>Notre entreprise a été fondée en 2020.</p>
<ul>
<li>42 clients accompagnés</li>
<li>15 pays</li>
</ul>
</main>
)
}
export default AboutPageRègle : "use client" se justifie uniquement pour useState, useEffect, useRef, les event handlers (onClick, onChange), ou les APIs navigateur (window, localStorage). Si aucun de ces éléments n'est présent, supprimer la directive.
Signal d'alerte en review : "use client" en haut d'un fichier sans useState, useEffect, ou event handler visible.
5. Happy path only
L'IA génère le chemin nominal. Elle ne valide pas les entrées, ne gère pas les erreurs, et suppose que chaque appel réussit.
// ❌ Happy path only
'use server'
const createOrder = async (formData: FormData) => {
const productId = formData.get('productId') as string
const quantity = Number(formData.get('quantity'))
const product = await db.product.findUnique({
where: { id: productId },
})
const order = await db.order.create({
data: {
productId,
quantity,
total: product.price * quantity,
},
})
return order
}Problèmes : as string sans validation (formData.get() peut retourner null), Number() sans vérification (NaN), product peut être null (crash sur product.price), pas de type de retour explicite.
// ✅ Validation et gestion des erreurs explicites
'use server'
import { z } from 'zod'
const createOrderSchema = z.object({
productId: z.string().uuid(),
quantity: z.number().int().positive().max(100),
})
type CreateOrderError =
| { type: 'validation'; issues: z.ZodIssue[] }
| { type: 'product_not_found' }
const createOrder = async (
formData: FormData
): Promise<Result<Order, CreateOrderError>> => {
const parsed = createOrderSchema.safeParse({
productId: formData.get('productId'),
quantity: Number(formData.get('quantity')),
})
if (!parsed.success) {
return {
success: false,
error: {
type: 'validation',
issues: parsed.error.issues,
},
}
}
const product = await db.product.findUnique({
where: { id: parsed.data.productId },
})
if (!product) {
return {
success: false,
error: { type: 'product_not_found' },
}
}
const order = await db.order.create({
data: {
productId: parsed.data.productId,
quantity: parsed.data.quantity,
total: product.price * parsed.data.quantity,
},
})
return { success: true, data: order }
}La différence : chaque cas d'erreur est explicite dans le type de retour. TypeScript force l'appelant à les gérer.
Pour approfondir le Result Pattern : Result Pattern vs Throw en TypeScript.
Signal d'alerte en review : une fonction asynchrone sans gestion d'erreurs, des as string sur des FormData.get(), un try-catch avec console.error comme seule gestion.
6. Dépendances fantômes et APIs obsolètes
L'IA invente des packages, importe des modules qui n'existent pas, ou utilise des APIs dépréciées. Ce problème est spécifique aux modèles de langage : ils mélangent les versions et les noms de packages de leur corpus d'entraînement.
Exemples récurrents :
| Ce que l'IA génère | Le problème | La réalité |
|---|---|---|
import { useRouter } from 'next/router' | API Pages Router | import { useRouter } from 'next/navigation' |
import { render } from 'react-dom' | Dépréciée React 18+ | import { createRoot } from 'react-dom/client' |
import { useQuery } from 'react-query' | Package renommé | import { useQuery } from '@tanstack/react-query' |
getServerSideProps | Pages Router | Server Components (async) |
next/image avec layout | Next.js 12 | fill, width/height depuis Next.js 13+ |
Signal d'alerte en review : tout import qui ne se résout pas au npm install, toute API qui semble venir d'une version antérieure du framework.
Vérification : dans le doute, consulter la documentation officielle. Un npx next info ou une lecture du package.json suffit à identifier les versions en place.
7. Duplication au lieu de réutilisation
L'IA ne connaît pas le codebase existant (sauf si le contexte lui est fourni explicitement). Elle recrée des fonctions, des types, et des composants qui existent déjà.
// ❌ L'IA recrée une fonction existante
const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR',
}).format(amount)
}Alors que src/utils/format.ts exporte déjà :
const formatPrice = (amount: number): string => {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR',
}).format(amount)
}
export { formatPrice }Deux fonctions identiques avec des noms différents, un codebase qui diverge, et un risque de comportement incohérent si l'une est modifiée sans l'autre.
Ce problème diminue avec les outils qui incluent le codebase dans le contexte (Claude Code avec un fichier CLAUDE.md bien configuré, Cursor avec des rules). Mais en review, la vérification reste nécessaire.
Signal d'alerte en review : une nouvelle fonction utilitaire dans un composant. Vérifier si un équivalent existe dans utils/, lib/, ou helpers/.
Checklist de review résumée
| # | Pattern | Signal d'alerte | Action |
|---|---|---|---|
| 1 | any et typage lâche | any, as, @ts-ignore | Exiger des types explicites |
| 2 | Composant monolithique | 100+ lignes, fetch + state + JSX | Séparer données, logique et rendu |
| 3 | useEffect orchestrateur | 2+ useEffect, setState dans un effect | Identifier l'alternative React 19 |
| 4 | "use client" par défaut | Directive sans hook ni event handler | Supprimer si pas de raison client |
| 5 | Happy path only | Pas de validation, as string | Ajouter Zod + Result Pattern |
| 6 | Dépendances fantômes | Import non résolu, API obsolète | Vérifier la doc officielle |
| 7 | Duplication | Nouvelle fonction utilitaire | Chercher l'existant dans le codebase |
Pour automatiser une partie de ces vérifications, configurer ESLint avec les règles adaptées : ESLint, Prettier, Husky : automatiser la qualité du code en 2026. Les patterns 1 et 3 peuvent être partiellement détectés par des règles ESLint (@typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps).
Les erreurs à éviter en tant que reviewer
Rejeter le code "parce que c'est de l'IA"
L'origine du code (IA ou humain) n'est pas un critère de review. Le code est bon ou mauvais indépendamment de qui l'a écrit. Si un composant est bien typé, bien structuré et bien testé, peu importe qu'il ait été généré par Claude ou écrit manuellement.
Reviewer ligne par ligne au lieu du flux global
Le risque avec le code IA : chaque ligne semble correcte, mais l'ensemble ne tient pas. Un composant peut avoir un typage correct, des noms explicites, et un JSX lisible, tout en ayant une architecture problématique (fetch dans un useEffect au lieu d'un Server Component).
Commencer par la structure : le code est-il au bon endroit ? Puis descendre dans les détails.
Ignorer les dépendances importées
Vérifier que les imports se résolvent et que les versions correspondent. C'est le point aveugle le plus fréquent : le reviewer lit la logique mais ne vérifie pas que les packages existent.
Confondre "ça compile" et "c'est correct"
TypeScript en mode strict attrape beaucoup de choses. Mais as any compile aussi. Un code qui passe le CI n'est pas nécessairement correct si les vérifications ont été contournées.
FAQ
Le code généré par IA est-il toujours problématique ?
Non. Le problème survient quand l'IA génère sans contexte : elle produit du code générique qui ne respecte pas les conventions du projet. La qualité du code IA dépend directement de la qualité des instructions fournies.
Faut-il reviewer plus sévèrement le code IA ?
Pas plus sévèrement, mais différemment. Les critères de qualité sont les mêmes. Mais les points d'attention changent : vérifier les types (l'IA met des any), les imports (dépendances inventées), et la structure (composants monolithiques). Un humain fait rarement ces erreurs-là.
Comment réduire ces patterns en amont ?
Trois leviers. (1) Fournir du contexte à l'IA (fichier CLAUDE.md, rules, conventions) pour qu'elle génère du code conforme dès le départ. (2) Configurer ESLint strict pour bloquer any, les hooks mal utilisés, les imports interdits. (3) Avoir des tests qui vérifient les comportements, pas l'implémentation. Ces trois filets réduisent le travail du reviewer.
Quels outils ESLint aident à détecter ces patterns ?
@typescript-eslint/no-explicit-any bloque les any explicites. react-hooks/exhaustive-deps détecte les useEffect mal configurés. @typescript-eslint/ban-ts-comment bloque les directives @ts-ignore et @ts-nocheck qui contournent le typage. Pour la configuration complète : ESLint, Prettier, Husky : automatiser la qualité du code en 2026.
Conclusion
Je pense que l'avenir de notre métier va être de plus en plus de relire du code plutôt que de l'écrire nous-mêmes. La capacité à analyser et comprendre du code est encore plus importante qu'avant.
Points clés
- Les 7 patterns problématiques sont récurrents et identifiables :
any, composants monolithiques,useEffect,"use client", happy path, dépendances fantômes, duplication. La review est le filet de sécurité. - Automatiser ce qui peut l'être (ESLint, TypeScript strict) pour concentrer la review humaine sur la logique et l'architecture.
- La qualité du code IA dépend du contexte fourni : conventions explicites, architecture documentée, contraintes techniques communiquées.

À propos de l'auteur
Je suis Dimitri Dumont, développeur freelance spécialisé React & Next.js depuis plus de 7 ans. J'ai accompagné 22 startups et réalisé 43 missions avec une note de 5/5. Je partage ici les pratiques que j'utilise en mission. En savoir plus →
Une question sur cet article ?
Cet article vous a été utile ?
Je peux vous accompagner sur votre projet React & Next.js.
Discutons de votre projet →