# 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 `