# Front-end — Risques & vigilance
Ce fichier recense des risques front-end susceptibles de provoquer :
- bugs subtils,
- comportements inattendus,
- dette technique rapide,
- régressions UX/perf/a11y.
Dernière mise à jour : 24-03-2026
---
## Règles d’utilisation
- Chaque entrée doit dire :
- ce qui peut mal se passer,
- comment on le voit (symptômes),
- comment on le maîtrise (mitigation).
- Si c’est lié à une stack / version : on note le contexte.
---
## Index
- [Auth côté client](#risque-auth-cote-client)
- [Erreurs silencieuses / écrans blancs](#risque-erreurs-silencieuses)
- [Mélange server state / client state](#risque-melange-server-client-state)
- [Appels API en state local d’écran](#risque-api-state-local-ecran)
- [Performances : sur-renders + bundle](#risque-performances-sur-renders)
- [Accessibilité oubliée (a11y)](#risque-accessibilite-oubliee)
- [Catch silencieux — erreur inconnue sans feedback utilisateur](#risque-catch-silencieux)
- [Auto-reset d’un état dégradé sur toute réponse 2xx](#risque-auto-reset-etat-degrade)
- [Refresh store en fire-and-forget après mutation](#risque-refresh-store-fire-and-forget)
- [Loading infini sur écran gated par droits distants](#risque-loading-infini-ecran-gated)
- [Jest React Native — config node bloque les composants `.tsx`](#risque-jest-rn-config-node)
- [Bouton OAuth présent mais handler vide après refacto UI](#risque-oauth-handler-vide)
- [Double système d'espacement dans un monorepo Expo](#risque-double-systeme-espacement)
- [Dimensions d'image via tokens `spacing` (React Native)](#risque-dimensions-image-via-spacing)
- [Écran détail Expo Router — store vide en deep link / reload](#risque-store-vide-deep-link)
- [`useEffect` fetch — guard incomplet sur les états terminaux](#risque-useeffect-guard-incomplet)
- [Store Zustand : collections sans clé de contexte (navigation inter-contexte)](#risque-zustand-collection-sans-cle-contexte)
- [`useSearchParams()` sans `Suspense` casse le build Next.js App Router](#risque-usesearchparams-sans-suspense)
- [Type `ViewData` dupliqué entre couche serveur et composant UI (Next.js)](#risque-type-viewdata-duplique)
- [Composant React dans un fichier `.ts` — `React.createElement` workaround](#risque-composant-react-fichier-ts)
- [Double validation de segment dynamique App Router (layout + page)](#risque-double-validation-segment-app-router)
- [Faux test négatif — tester le helper au lieu de tester l'exclusion](#risque-faux-test-negatif)
- [État booléen UI dérivé hardcodé au lieu d'être calculé depuis le store](#risque-boolean-ui-hardcode-store)
- [Flag `isLoading` unique pour des opérations de natures différentes](#risque-flag-isloading-unique-nature-differente)
- [Consent state : `false` ambigu entre "pas de décision" et "refus explicite"](#risque-consent-state-false-ambigu)
- [Script inline : interpolation directe au lieu de `JSON.stringify`](#risque-script-inline-interpolation-directe)
- [Next.js App Router : `window.location.reload()` au lieu de `router.refresh()`](#risque-window-location-reload-nextjs)
- [`useTransition` + optimistic update : snapshot capturé après `setState`](#risque-usetransition-snapshot-apres-setstate)
- [`window.confirm()` dans une app React/Next.js](#risque-window-confirm-react)
- [`import type` depuis `src/server/**` dans un composant client](#risque-import-type-server-composant-client)
- [Inline styles dans les composants dashboard](#risque-inline-styles-dashboard)
- [Classes Tailwind invalides courantes (bugs silencieux)](#risque-tailwind-classes-invalides)
- [Next.js : `` natif interdit dans les composants](#risque-img-natif-nextjs)
- [`useTransition` global pour des listes d'items interactifs](#risque-usetransition-global-liste-items)
- [`useCallback` inutile quand le callback est wrappé en inline au render](#risque-usecallback-inutile-inline)
- [Formulaire React avec `defaultValue` sans `key` prop](#risque-formulaire-defaultvalue-sans-key)
- [Zustand : optimistic update sur item absent de la liste principale](#risque-zustand-optimistic-update-sous-listes)
- [Guard de rôle via return conditionnel dans le render](#risque-guard-role-return-conditionnel)
- [Méthodes Zustand qui avalent les erreurs (sans rethrow)](#risque-zustand-sans-rethrow)
- [Regex globale singleton (/g) partagée au niveau module](#risque-regex-globale-singleton)
---
## Auth côté client (mauvaise séparation des responsabilités)
### Risques
- Le front “décide” des permissions au lieu d’appliquer un contrat backend
- Affichage d’actions interdites / fuite d’informations dans l’UI
- Tokens stockés de façon dangereuse (XSS)
### Symptômes
- Différences entre “ce que l’UI permet” et “ce que l’API accepte”
- Bugs “ça marche chez moi” selon sessions/rôles
- Incohérences sur refresh / multi-tab
### Bonnes pratiques / mitigations
- Le backend reste source de vérité (authz)
- Cacher l’UI ≠ sécuriser : toujours sécuriser côté API
- Stockage tokens : privilégier cookies httpOnly si modèle adapté
- Gérer proprement expiration/refresh + révocation
### Contexte technique
- Observé : (à compléter)
- Stack : (à préciser)
---
## Erreurs silencieuses / écrans blancs
### Risques
- Exceptions non gérées → app inutilisable
- États async mal gérés → UI incohérente (loading infini, vide incompris)
### Symptômes
- Écran blanc après une action
- Toast générique “Une erreur est survenue” sans corrélation
- Pas de moyen de reproduire / diagnostiquer
### Bonnes pratiques / mitigations
- Pattern “états UI explicites” (loading/empty/error)
- Boundary d’erreur UI + fallback
- Logging minimal côté client avec requestId/traceId quand possible
---
## Mélange server state / client state
### Risques
- Cache pollué par des états UI (onglets, filtres)
- UI qui reflète une donnée périmée sans le savoir
- Re-renders et bugs de synchronisation
### Symptômes
- “Ça revient tout seul” après refresh
- Données affichées ≠ données du backend
- Debug très long car état implicite
### Bonnes pratiques / mitigations
- Séparer explicitement server state vs client state
- Invalidation/reload explicite du server state
- État UI local réinitialisable
---
## Appels API gérés en state local d’écran (refactor coûteux)
### Risques
- Server state non partageable entre écrans (liste/detail, wizard, tabs) → duplication et incohérences
- Pas de cache / invalidation standard → bugs subtils et re-fetchs inutiles
- Refactor tardif quand l’epic s’étend (mutations, cache, offline, pagination)
### Symptômes
- Même appel API recopié dans plusieurs écrans
- Un écran “A” modifie une ressource mais l’écran “B” n’est jamais rafraîchi
- Code review qui force un refactor vers un store/cache au milieu d’un epic
### Bonnes pratiques / mitigations
- Par défaut : créer un store de domaine (ex : Zustand) ou un cache de server state pour tout domaine susceptible d’être réutilisé
- Centraliser `isLoading`/`error`/`data` et la stratégie de refresh/invalidation
- Exception acceptable : état purement UI, local et jetable (ex : input de recherche, filtres temporaires non persistés)
---
## Performances : sur-renders + bundle non maîtrisé
### Risques
- App lente sur mobile
- Bundle qui grossit sans contrôle
- Chargements inutiles (images, libs)
### Symptômes
- Input lag
- Temps de chargement qui dérive à chaque feature
- Requêtes réseaux inutiles
### Bonnes pratiques / mitigations
- Lazy loading routes/features
- Mesurer (au minimum) : temps de chargement + re-renders critiques
- Politique images (formats, tailles, lazy)
- Audit régulier des dépendances
---
## Accessibilité oubliée (a11y)
### Risques
- App inutilisable au clavier/lecteur d’écran
- Régressions silencieuses sur focus/labels
### Symptômes
- Modales impossibles à fermer au clavier
- Inputs sans labels/erreurs non annoncées
- Focus “perdu”
### Bonnes pratiques / mitigations
- Checklist a11y minimale sur chaque écran clé
- Gestion de focus (modales, erreurs formulaire)
- Labels/aria cohérents + tests simples
---
## Catch silencieux — erreur inconnue sans feedback utilisateur
### Risques
- Un `catch` qui ne traite que les cas connus laisse l'utilisateur face à un spinner qui disparaît sans message
- L'état d'erreur reste implicite → impossible de diagnostiquer ou de reproduire
### Symptômes
- Bouton spinner qui s'arrête, rien ne se passe
- Pas de toast / message d'erreur affiché
- Erreur "avalée" silencieusement dans les logs
### Bonnes pratiques / mitigations
```typescript
} catch (err: unknown) {
const code = (err as { code?: string }).code;
if (code === 'SUBSCRIPTION_REQUIRED') {
setSubscriptionRequired(true);
} else {
setError('Une erreur est survenue. Veuillez réessayer.'); // toujours un fallback
}
}
```
- **Règle** : tout `catch` doit avoir une branche `else` (ou `default`) qui affiche un feedback utilisateur explicite.
- Contexte technique : React Native / Expo — 09-03-2026
---
## Auto-reset d’un état dégradé sur toute réponse 2xx
### Risques
- Le client sort trop tôt d’un mode dégradé alors que la cause serveur est toujours présente
- Le bandeau ou l’état read-only clignote puis disparaît à tort
- Les utilisateurs retentent une action d’écriture qui va encore échouer
### Symptômes
- Un GET réussi réinitialise `isReadOnly` ou `isDegraded`
- L’UI redevient “normale” alors que Redis ou un service critique est toujours indisponible
- Les erreurs reviennent immédiatement à la prochaine mutation
### Bonnes pratiques / mitigations
- Ne réinitialiser l’état dégradé qu’après une requête d’écriture réussie
- Exclure `GET` et `HEAD` de la logique de reset
- Conserver le mode dégradé tant qu’aucune mutation n’a prouvé le retour à la normale
- Contexte technique : React Native / Expo — 10-03-2026
---
## Refresh store en fire-and-forget après mutation
### Risques
- L’UI affiche un succès alors que la resynchronisation a échoué
- État local incohérent avec l’état serveur
- Erreurs silencieuses impossibles à diagnostiquer
### Symptômes
- Mutation réussie puis store jamais rafraîchi
- Spinner coupé avant que l’écran soit réellement à jour
- Données anciennes qui persistent jusqu’au prochain reload
### Bonnes pratiques / mitigations
- `await` explicite du refresh si l’UI dépend du résultat
- Gestion d’erreur dédiée sur la phase de resynchronisation
- N’utiliser le fire-and-forget que pour un effet secondaire réellement non bloquant
- Contexte technique : React Native / Expo — 10-03-2026
---
## Loading infini sur écran gated par droits distants
### Risques
- Un écran protégé reste bloqué dans un faux `loading` après une erreur de chargement des entitlements
- Un effet relance automatiquement la récupération en boucle sans action utilisateur
- L’utilisateur ne voit ni état d’erreur ni issue de sortie claire
### Symptômes
- Spinner infini sur un écran soumis à permissions distantes
- `entitlements` ou autorisations laissés à `null` après erreur
- `useEffect` ou logique d’entrée qui retrigger le fetch à chaque rendu
### Bonnes pratiques / mitigations
- Distinguer explicitement `loading`, `error`, `ready`
- Ne pas réutiliser `null` comme état ambigu "pas encore chargé" et "chargement en erreur"
- Bloquer les retries automatiques en boucle après erreur
- Réautoriser un retry seulement via action utilisateur explicite ou nouvelle condition d’entrée
- Contexte technique : React Native / Expo / store d’entitlements — 10-03-2026
---
## Jest React Native — config node bloque les composants `.tsx`
### Risques
- `SyntaxError: Cannot use import statement outside a module` lors de l’import d’un barrel `.ts` qui réexporte des `.tsx`
- Impossible d’importer des composants React Native dans les tests — JSX non transformé
### Symptômes
- Erreur de syntaxe inattendue au run des tests sur un fichier `.ts` qui importe un `.tsx`
- Les tests de tokens passent mais tout test touchant un composant échoue
### Bonnes pratiques / mitigations
- `transform: { ‘^.+\\.ts$’: ‘ts-jest’ }` ne transforme que `.ts` — pas `.tsx`
- **Pattern recommandé** : tester la logique pure (tokens, valeurs de style) dans `.spec.ts`, le rendu visuel dans `.spec.tsx` avec une config séparée (`@testing-library/react-native` + `babel-jest`)
- Exporter le `StyleSheet` de chaque composant pour le tester sans JSX (voir pattern dédié dans `10_frontend_patterns_valides.md`)
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie 19-03-2026
---
## Bouton OAuth présent mais handler vide après refacto UI
### Risques
- L’OAuth est silencieusement cassé sur le nouvel écran — zéro erreur au démarrage, zéro crash
- L’AC "toutes les fonctionnalités préservées" peut être coché alors que le bouton est mort
### Symptômes
- `