# Frontend — Risques & vigilance : Next.js
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
## `useSearchParams()` sans `Suspense` casse le build Next.js App Router
### Risques
- Un composant client utilisant `useSearchParams()` peut provoquer un échec de prerender/build s'il est rendu sans boundary `Suspense` depuis la page/layout serveur
### Symptômes
- `Error: useSearchParams() should be wrapped in a suspense boundary` au `next build`
- Fonctionne en dev mais échoue à la CI/CD
### Bonnes pratiques / mitigations
- Isoler le composant client qui utilise `useSearchParams()` et le rendre sous `` au niveau de la page
- Ne jamais appeler `useSearchParams()` directement dans un composant rendu sans `Suspense` depuis un Server Component
- Contexte technique : Next.js App Router récent / Turbopack — app-template-resto 16-03-2026
---
## Type `ViewData` dupliqué entre couche serveur et composant UI (Next.js)
### Risques
- TypeScript accepte deux structures identiques par structural typing — si le type source évolue, la couche UI reste désynchronisée sans erreur de compilation tant que les formes correspondent
### Symptômes
- Deux définitions du même type dans `src/server/` et `src/app/`
- Champ ajouté côté serveur mais absent dans le composant UI sans warning
### Bonnes pratiques / mitigations
```typescript
// ✅ La couche UI importe et re-exporte
export type { PublicHomeViewData } from "@/server/public/getPublicHomeData";
// ❌ À éviter — redéfinition locale
export type PublicHomeViewData = { tenantName: string; ... };
```
- Règle : le type appartient à la couche qui le produit. La couche UI importe uniquement.
- Contexte technique : Next.js App Router / TypeScript — app-template-resto 16-03-2026
---
## Composant React dans un fichier `.ts` — `React.createElement` workaround
### Risques
- Code illisible vs JSX natif
- Fausse impression que le fichier est "sans JSX" — peut tromper les outils de linting et les reviewers
- Empêche l'utilisation de la syntaxe JSX si on doit ajouter des enfants complexes
### Symptômes
- `React.createElement(...)` dans un fichier `.ts`
### Bonnes pratiques / mitigations
- Tout fichier exportant une fonction retournant un `ReactElement` ou utilisant React doit avoir l'extension `.tsx`
- Sans exception
- Contexte technique : TypeScript / React — app-template-resto 16-03-2026
---
## Double validation de segment dynamique App Router (layout + page)
### Risques
- Si le layout fait `notFound()` sur un segment invalide ET que la page répète la même condition, les deux deviennent désynchronisés silencieusement lors d'une modification
### Symptômes
- Même condition de validation dans `layout.tsx` et `page.tsx` d'un même segment
- Modification du layout n'est pas reportée dans la page (comportement divergent)
### Bonnes pratiques / mitigations
- Si le layout garde, la page consomme — une seule responsabilité par couche
- La page doit faire confiance à son layout parent
- **Règle** : un seul composant est responsable de la garde sur un segment dynamique
- Contexte technique : Next.js App Router — app-template-resto 17-03-2026
---
## Next.js App Router : `window.location.reload()` au lieu de `router.refresh()`
### Risques
- Full reload = perd l'état React, navigation complète, plus lent
- `router.refresh()` est l'outil idoine : retrigger le fetch des Server Components sans détruire l'état client
### Symptômes
- `window.location.reload()` après un Server Action dans un Client Component
- Flash de rechargement visible, perte de l'état local (scroll, focus, état de formulaire)
### Bonnes pratiques / mitigations
```tsx
// ❌ Anti-pattern — full reload
await createCategoryAction(formData);
window.location.reload();
// ✅ Pattern correct — RSC diff, préserve l'état client
const router = useRouter();
await createCategoryAction(formData);
router.refresh();
```
- `router.refresh()` refetch uniquement les Server Components affectés (via `revalidatePath`) et applique un diff. L'état des Client Components est préservé.
- Contexte technique : Next.js App Router — app-template-resto 21-03-2026
---
## Consent state : `false` ambigu entre "pas de décision" et "refus explicite"
### Risques
- Sans champ `decided`, `analytics: false` peut signifier "première visite" ou "refus explicite" — indistinguables
- Le banner de consentement réapparaît à chaque visite après un refus, violant l'AC de persistance du choix
### Symptômes
- Banner qui réapparaît après rechargement malgré un refus explicite
### Bonnes pratiques / mitigations
```typescript
type ConsentState = {
analytics: boolean;
decided: boolean; // true = l'utilisateur a fait un choix (cookie présent)
};
const DEFAULT: ConsentState = { analytics: false, decided: false };
// À la lecture du cookie :
if (!cookieValue) return DEFAULT; // decided=false (première visite)
return { analytics: parsed.analytics, decided: true };
```
- L'état initial du banner doit être `!decided`, pas `!analytics`
- Contexte technique : Next.js / cookies — app-template-resto 21-03-2026
---
## Script inline : interpolation directe au lieu de `JSON.stringify`
### Risques
- Injection XSS potentielle via une valeur de configuration interpolée directement dans un `