La dette frontend en tant que coût des changements : diagnostiquer une base de code React en expansion
Apprenez à repérer les décisions cumulatives qui rendent les applications volumineuses basées sur React, Redux, TypeScript et Next.js coûteuses à modifier, ainsi que la manière de réduire progressivement cette dette technique.
Sources: Frontend Technical Debt: The Problems Nobody Notices Until the React App Becomes Huge
Les grandes applications React ne deviennent presque jamais difficiles à maintenir en raison d’un seul commit catastrophique. Elles se détériorent au fil d’une longue série de choix petits, chacun semblant raisonnable en soi : un useEffect supplémentaire, une partie de l’état Redux qui n’avait pas besoin d’être globale, un composant « temporaire » de 500 lignes, un widget partagé qui continue d’accumuler des props, un fichier utils qui regroupe tout ce qui n’a pas de place évidente, un type API collé dans trois modules, une convention de stylisation que personne n’ose remettre en question. Aucune de ces décisions ne semble erronée au moment où elle est prise. Les problèmes commencent lorsque des centaines d’entre elles s’accumulent et commencent à interagir.
Cette accumulation est ce que nous entendons par dette technique frontend, et l’idée principale de ce guide est simple : les plus gros problèmes frontend sont rarement causés par un seul morceau de code défectueux, mais par des décisions mineures qui s’accumulent. Ci-dessous, vous trouverez les formes les plus courantes que prend cette dette dans des bases de code React, Redux, TypeScript et Next.js, pourquoi chacune devient coûteuse, ainsi qu’une méthode pratique pour la réduire sans interrompre le travail sur le produit pour une refonte.
Comment un répertoire propre se détériore
Un nouveau projet commence généralement avec un organisateur que n’importe qui peut lire d’un coup d’œil :
src/
components/
pages/
hooks/
services/
store/
Un développeur qui n’a jamais vu le répertoire peut deviner où un nouveau hook ou service devrait être placé. Ensuite, le produit s’élargit, l’équipe s’agrandit et la liste des fonctionnalités augmente. Quelques années plus tard, le même arborescence ressemble souvent à ceci :
src/
components/
Button/
Modal/
Table/
UserTable/
UserTableNew/
CommonTable/
GenericTable/
SharedTable/
...
hooks/
useUser.ts
useUsers.ts
useUserData.ts
useFetchUser.ts
useUserDetails.ts
...
utils/
helpers.ts
common.ts
misc.ts
formatter.ts
newHelpers.ts
oldHelpers.ts
store/
user/
users/
userData/
userProfile/
...
Remarquez le schéma : plusieurs tables aux fonctions chevauchantes, cinq points d’ancrage qui récupèrent tous un « utilisateur » de manières légèrement différentes, des fichiers d’aide distingués uniquement par les mots « nouveau » et « ancien », ainsi que quatre dossiers de stockage pour ce qui devrait être un seul domaine. Personne n’a conçu cela. Il a évolué, pull request après pull request, ce qui explique précisément pourquoi ce type de dette est dangereux : il n’y a jamais de moment où il devient évident qu’il faut le corriger.
Le raccourci qui devient silencieusement permanent
La source la plus courante de dette est le nettoyage différé. Une fonctionnalité doit être livrée aujourd’hui ; une implémentation soignée prendrait deux jours, tandis qu’une version rapide en nécessiterait trois heures. Choisir la version de trois heures n’est pas automatiquement un mauvais choix technique. Parfois, c’est précisément la décision commerciale appropriée.
Le risque apparaît lorsque le raccourci dépasse la durée de validité de sa raison d’être. Le code temporaire a une forte tendance à se transformer en infrastructure de production, et les décisions qui le créent semblent généralement inoffensives sur le moment :
"Mettons simplement ça dans le composant existant." ">Nous refactorerons cela après la publication." ">Pour l’instant, gardons-le dans Redux." ">Nous pourrons le généraliser plus tard." ">Rien d’autre n’aura jamais besoin de ça."
Six mois plus tard, le contexte a disparu. Personne ne se souvient pourquoi ce code a été écrit de cette manière, la personne qui l’a écrit a pu être transférée dans une autre équipe, les exigences qu’il servait ont changé, et trois autres fonctionnalités dépendent désormais de son comportement. Annuler un raccourci qui a pris trois heures peut alors prendre trois semaines.
Lorsqu’un composant gère toute une fonctionnalité
Le « composant en surcharge » est l’un des symptômes les plus reconnaissables. Il commence généralement par une liste raisonnable de tâches esquissées au sein d’une seule fonction :
function Checkout() {
// fetch products
// validate coupon
// calculate totals
// manage address
// handle payment
// track analytics
// show modals
// handle errors
// render UI
}
Chaque nouvelle exigence se retrouve au même endroit, car c’est là que le code correspondant existe déjà. Finalement, ce composant gère :
- la communication avec l’API
- les règles métier et la validation
- la gestion de l’état
- l’analyse et les vérifications d’accès
- la navigation et la gestion des erreurs
- le stylisme et l’affichage
React n’est pas vraiment en cause ici. Le véritable problème réside dans le fait que trop de responsabilités se sont étendues au-delà des limites d’un composant. Un composant est un endroit approprié pour l’affichage et pour gérer l’état de l’interface utilisateur qui lui appartient ; il ne devrait pas devenir le conteneur pour toutes les fonctionnalités auxquelles une fonctionnalité donne accès. Les règles de tarification, la validation des coupons et les analyses peuvent être placées dans des modules simples ou des hooks personnalisés qui peuvent être testés sans afficher quoi que ce soit.
Composants réutilisables qui coûtent plus qu’ils ne rapportent
Débutant dans sa carrière, la réutilisabilité semble être un avantage indéniable. Avec de l’expérience, il devient clair que chaque abstraction a un prix. Prenons par exemple une table qui a été rendue « configurable » :
<DataTable
data={data}
columns={columns}
loading={loading}
selectable
searchable
sortable
pagination
serverPagination
expandable
virtualized
stickyHeader
renderEmptyState={...}
renderLoadingState={...}
onRowClick={...}
/>
D’un point de vue technique, il est réutilisable. Sa maintenabilité est une question différente, et la réponse honnête est « cela dépend ». Chaque drapeau booléen double le nombre de combinaisons que le composant doit gérer, et chaque nouvel utilisateur nécessitant une légère modification ajoute un autre attribut plutôt qu’un nouveau composant.
Un test plus utile que « peut-on le réutiliser ? » est de savoir si l’abstraction facilitera la création de la prochaine fonctionnalité. Un composant comportant trente attributs n’est souvent pas du tout une architecture réutilisable ; il s’agit plutôt de plusieurs composants distincts regroupés derrière une seule API. La composition, où les utilisateurs assemblent des éléments plus petits, constitue fréquemment une meilleure solution, comme expliqué dans la façon de résoudre le surcharge des attributs React grâce à la composition et aux slots.
Où le dossier utils cache votre domaine
Presque tous les frontsends de taille moyenne ou importante développent un répertoire utils, et au début cela ne pose pas de problème. Avec le temps, il prend généralement cet aspect :
utils/
formatDate.ts
formatCurrency.ts
userHelpers.ts
permissions.ts
productHelpers.ts
validation.ts
apiHelpers.ts
stringUtils.ts
common.ts
helpers.ts
Lorsque le dossier atteint cet état, personne ne sait où placer de nouvelles logiques ; par conséquent, chaque fonction utilitaire est ajoutée dans common.ts, puis celle d’après aussi. Le code est organisé par type de fichier, mais le domaine métier reste invisible. Les vérifications de permissions, les règles relatives aux produits et les interactions avec l’API se retrouvent côte à côte avec le formatage de chaînes de caractères.
La meilleure question à se poser n’est pas « Dans quel fichier utilitaire cela doit-il être placé ? », mais plutôt « Quelle capacité métier gère cette logique ? » Une organisation axée sur les fonctionnalités permet de donner une réponse claire à cette question :
features/
checkout/
components/
hooks/
services/
types/
utils/
products/
components/
hooks/
services/
types/
payments/
components/
hooks/
services/
types/
Ce n’est pas une obligation d’adopter des dossiers par fonctionnalité partout. Le principe fondamental est plus important que les noms des dossiers : le code doit être regroupé en fonction de sa propriété et de ses responsabilités, et non simplement en fonction du type de fichier qu’il représente.
Redux en tant que stockage global
Redux est un outil véritablement utile, mais placer tout le contenu dans l’état global constitue l’un des moyens les plus rapides de créer une dette frontend. Le stockage commence généralement par des parties qui justifient clairement d’être globales :
auth
user
permissions
cart
products
checkout
notifications
Puis il intègre des éléments qui concernent en réalité un seul écran ou une seule interaction :
modal
filters
selectedTab
searchText
formState
temporaryUIState
À terme, le stockage se comporte comme une base de données à l’échelle de l’application pour tout ce que deux composants pourraient souhaiter lire. Techniquement, presque n’importe quelle valeur peut être stockée dans Redux, donc la question « est-ce possible ? » n’est pas pertinente. La vraie question est de savoir si l’état doit réellement être global. Classer l’état en trois catégories aide à y voir plus clair.
État de l’interface utilisateur qui doit rester local
De telles valeurs appartiennent généralement au composant qui les utilise, via useState ou useReducer:
isModalOpen
activeTab
inputValue
hoveredItem
expandedRow
Le fait de les transférer dans un stockage global rend chaque modal ouvert ou ligne survolée une préoccupation pour toute l’application et ajoute de l’indirection à des interactions simples.
État serveur nécessitant une stratégie de mise en cache
Les données gérées par le backend ont des besoins différents :
products
orders
users
invoices
Ici, les problèmes difficiles sont le cache, la déduplication, le rechargement et l’invalidation. Ceux-ci exigent une approche délibérée de récupération des données (une bibliothèque dédiée à l’état du serveur ou un pattern équivalent) plutôt que des reducers écrits manuellement qui copient les réponses dans le stockage. Les compromis sont explorés plus en détail dans rethinker l’état du serveur dans les grandes applications avec React Query et Redux.
État véritablement global à l’application
Certaines préoccupations concernent réellement l’ensemble du produit :
authentication
permissions
application-wide preferences
cross-feature workflows
Ces éléments constituent des candidats raisonnables pour un stockage global. La leçon n’est pas de « contourner Redux » ; elle est plutôt que la seule disponibilité ne constitue pas une raison de rendre l’état global.
Dette TypeScript : types qui perdent tout sens
TypeScript est l’un des outils les plus précieux pour les grands projets frontend, mais il ne conçoit pas à votre place la structure de votre codebase. Il est tout à fait possible d’appliquer ce schéma dans toute une application et de qualifier néanmoins le projet comme étant typé :
const data: any = response.data;
Chaque utilisation de any désactive les vérifications précisément au moment où les données entrent dans l’application, de sorte que le compilateur ne peut pas vous avertir en cas de modification de la structure de l’API. Une autre erreur fréquente consiste à déclarer le même type de domaine indépendamment dans plusieurs fichiers :
type User = {
id: string;
name: string;
}
Si l’on ajoute cela à plusieurs équipes, on obtient une série de versions presque identiques :
User
UserType
UserModel
UserResponse
UserDTO
UserData
Les développeurs consacrent alors plus d’efforts à décider quel type importer plutôt qu’à tirer parti du système de types. Viser le principe selon lequel « tout a un type » manque son objectif. L’objectif est que les contrats importants du système, tels que les réponses API, les entités de domaine et les interfaces des composants, soient explicites et définis une seule fois. Un bon TypeScript rend difficile la représentation des états invalides ; un mauvais TypeScript ne fait qu’engendrer davantage de fichiers. Une distinction utile consiste à conserver un type de transport pour ce que l’API renvoie et un type de domaine pour ce avec quoi l’interface utilisateur travaille, les convertissant en un seul endroit, au lieu de six variantes peu liées entre elles.
Lorsque useEffect devient l’issue de secours par défaut
Les effets sont une autre source fréquente de dettes qui s’accumulent progressivement. Tout commence par un seul effet qui réagit à une seule valeur :
useEffect(() => {
// do something
}, [something]);
Puis un deuxième effet maintient deux valeurs en synchronisation :
useEffect(() => {
// synchronize something
}, [a, b]);
Un troisième existe principalement pour corriger le comportement d’un précédent :
useEffect(() => {
// fix previous effect
}, [data]);
Le composant se transforme en une chaîne d’effets qui déclenchent d’autres effets, ce qui est difficile à suivre et sujet à des rendus supplémentaires ainsi qu’à des erreurs d’ordre. À ce stade, être compétent en React dépend moins de la connaissance de l’API que d’une vision claire du flux des données à travers le composant.
Un exemple typique est de stocker des données dérivées dans l’état et de les synchroniser avec un effet :
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Cela rend une fois avec une chaîne vide obsolète, puis exécute l’effet, avant de rendre à nouveau. La valeur peut tout simplement être calculée pendant le rendu :
const fullName = `${firstName} ${lastName}`;
Cela semble être une différence mineure, mais au sein d’une application, ces petites décisions ont un impact cumulé. Les effets servent à synchroniser avec des systèmes externes à React, tels que les abonnements, les temporiseurs ou le DOM, et non à dériver un état à partir d’un autre. Une analyse plus approfondie se trouve dans arrêter la synchronisation de l’état avec useEffect.
La frontière serveur/client dans Next.js
Next.js moderne ajoute une nouvelle dimension aux décisions architecturales. En plus de déterminer quel composant affiche quelque chose, il faut décider où le code s’exécute : sur le serveur, sur le client, ou les deux. Cette frontière constitue elle-même un choix architectural.
Une erreur fréquente consiste à convertir une grande partie de l’arborescence en composants clients simplement parce qu’un petit élément nécessite une interactivité. La directive doit être placée en haut du fichier de page :
"use client";
Et toute la page est maintenant envoyée au navigateur sous forme de JavaScript côté client :
export default function ProductPage() {
// entire page becomes client-side
}
Tout cela parce qu’un seul bouton a besoin d’un état local :
const [isOpen, setIsOpen] = useState(false);
C’est pratique, mais cette commodité à un niveau entraîne des coûts ailleurs : plus de JavaScript est envoyé au navigateur, et la récupération de données qui aurait pu rester sur le serveur doit désormais se faire côté client. En termes d’App Router, tout ce qui est importé depuis un fichier "use client" fait partie du bundle côté client, de sorte que l’emplacement de la directive influence bien plus que le seul composant que l’on voulait rendre interactif.
Il est utile de penser en couches, en plaçant l’interactivité aussi bas dans la hiérarchie que possible :
Server
↓
Data fetching
↓
Feature boundary
↓
Interactive client component
↓
Small local state
plutôt qu’avec cette solution simpliste et linéaire :
Everything → Client Component
En pratique, cela signifie généralement extraire le bouton interactif dans son propre petit composant client et conserver la page, son chargement de données ainsi que la majeure partie de sa structure HTML sur le serveur.
La dette en matière de stylisation reste une dette
Les équipes traitent souvent la dette technique comme un problème lié à JavaScript et TypeScript. La stylisation accumule également des dettes de manière tout aussi fiable, surtout dans les produits à long terme qui combinent plusieurs approches en même temps : CSS-in-JS et styled-components, modules CSS, classes utilitaires, anciennes feuilles de style globales, et plus d’un système de conception. Tôt ou tard, la bibliothèque de composants contient quelque chose comme ceci :
Button
ButtonV2
PrimaryButton
NewButton
StyledButton
CommonButton
LegacyButton
et personne ne peut dire quel bouton est le bon. La technologie de stylisation n’est que rarement la cause racine. Le véritable problème réside dans l’absence de responsabilité claire pour le système de conception et d’limites bien définies entre les composants. Un frontend mature dispose de réponses aux questions telles que :
- Quels composants font partie de l’ensemble public et partagé ?
- Quels composants n’existent que pour une fonctionnalité spécifique ?
- Quelle équipe est responsable de chacun d’eux ?
- Quelle est la méthode approuvée pour ajouter une variante ?
- Comment les changements perturbateurs sont-ils déployés ?
- Quels éléments peuvent être inclus dans le système de conception ?
- Qu’est-ce qui doit rester local et ne jamais être promu au statut partagé ?
Lorsque la duplication l’emporte sur l’abstraction
Cette leçon est souvent la plus difficile à accepter pour les développeurs expérimentés. Supposons qu’il y ait deux composants qui ressemblent actuellement à 90 % l’un à l’autre :
ComponentA
ComponentB
L’instinct est de les fusionner en un seul :
<UniversalComponent />
Cependant, la duplication est parfois l’option la moins coûteuse, car on ne sait pas encore si les deux éléments continueront d’évoluer dans la même direction. S’ils divergent, le composant fusionné se remplit de conditions logiques, et toute modification dans l’un des appels risque de briser l’autre. Une petite quantité de duplication est bénéfique ; une abstraction prématurée peut créer un couplage qui dure des années.
Règle de base pratique : écrire le code deux fois, étudier le schéma qui en ressort, et ne pas abstraire avant qu’un troisième cas d’usage n’ait confirmé sa structure. Ce n’est pas une loi universelle, mais c’est une règle de départ sensée.
Dépendances qui ne justifient plus leur présence
Les interfaces utilisateur volumineuses accumulent rapidement des dépendances. Une base de code vieille de cinq ans peut facilement inclure des bibliothèques pour :
- la gestion de l’état, les formulaires et la validation
- les dates, les graphiques et les animations
- les kits d’interface utilisateur et le stylisme
- la récupération de données
- les tests
package.json
La propreté des dépendances ne concerne pas seulement l’application de correctifs de sécurité. Cela signifie également s’interroger périodiquement sur le fait que chaque dépendance justifie encore son existence, compte tenu des coûts de maintenance, de mise à jour et d’emballage qu’elle entraîne.
Le vrai prix : des changements plus lents et plus risqués
Le « dettes » de code n’est pas un problème parce que les ingénieurs trouvent le code désordonné désagréable. C’est un problème parce qu’il modifie les aspects économiques liés à la livraison de logiciels. Dans une application saine, une requête suit un parcours court :
Feature request
↓
Small change
↓
Tests
↓
Deploy
Dans une application fortement endettée en code, la même requête devient l’objet d’une enquête approfondie :
Feature request
↓
Find where this behavior lives
↓
Understand 5-year-old code
↓
Check Redux dependencies
↓
Check shared component
↓
Check side effects
↓
Check styling overrides
↓
Change code
↓
Something else breaks
↓
Fix regression
↓
Add another workaround
Chaque étape de cette deuxième chaîne représente du temps consacré à la compréhension plutôt qu’à la création, et les deux dernières étapes sont particulièrement révélatrices : un changement endommage quelque chose de sans rapport, et la correction nécessite encore une solution de contournement. C’est ainsi que la base de code génère des « intérêts », et c’est bien cela la dette technique.
Pourquoi « ça marche toujours » est un signal trompeur
« L’application fonctionne-t-elle ? » est donc un test de santé insuffisant. Une question bien plus utile, surtout à mesure que les systèmes se développent, est : combien coûte-t-il de modifier cette application en toute sécurité ?
Réduire la dette technique sans réécriture complète
La réponse tentante face à une base de code en désordre est de tout reconstruire. Le résultat typique, six mois plus tard, est prévisible :
- L’application existante continue de fonctionner en production
- Le remplaçant n’est qu’en partie terminé
- Deux systèmes nécessitent désormais des maintenances
- L’équipe est épuisée
- Le travail sur les produits est au point mort
Les améliorations architecturales progressives fonctionnent presque toujours mieux. Elles commencent par identifier l’endroit où se trouve réellement le problème.
Localiser les zones coûteuses
Résistez à l’envie de refactorer tout le code. Identifiez plutôt les parties du produit où les développeurs rencontrent régulièrement des difficultés, par exemple :
Checkout
User Management
Search
Dashboard
Reporting
Pour chaque candidat à la refacturation, cherchez ces signaux :
- Bugs qui réapparaissent constamment dans la même zone
- Fichiers que les gens hésitent à modifier
- Composants présents dans presque toutes les demandes de fusion
- Parties du produit que les nouveaux employés mettent le plus de temps à maîtriser
- Fonctionnalités dont les modifications affectent des modules non liés
Ces zones qui reviennent sans cesse constituent vos points critiques en termes de dette technique.
Évaluez le coût des modifications aujourd’hui
Aucun outil sophistiqué n’est nécessaire. Il suffit de suivre un petit nombre d’indicateurs :
Average time to implement feature
Average PR size
Number of files touched
Regression frequency
Build time
Test execution time
Deployment frequency
Lier la dette aux résultats d’ingénierie de ce type rend beaucoup plus facile l’établissement des priorités, car on peut montrer qu’une zone particulière ralentit la livraison plutôt que d’affirmer que son code est peu attrayant.
Définissez les règles de dépendance avant de choisir un modèle
Au lieu de débattre de l’architecture propre ou du design par fonctionnalités, répondez d’abord à une question plus fondamentale : que peut dépendre de quoi ? Une orientation en couches simple pourrait ressembler à ceci :
app
↓
features
↓
shared
Dans ce cadre, une fonctionnalité peut dépendre de blocs de construction partagés :
features/product
↓
shared/ui
shared/lib
mais le code partagé ne doit en aucun cas interférer avec une fonctionnalité spécifique :
shared/ui
X
features/product
L’architecture spécifique peut évoluer avec le temps ; c’est la direction des dépendances qui en assure la cohérence. Dès que l’interface utilisateur partagée commence à importer des éléments provenant d’une fonctionnalité du produit, elle n’est plus partagée, et chaque utilisateur dépend silencieusement de cette fonctionnalité. Il est également facile d’appliquer automatiquement ces règles à l’aide de règles de linting sur les chemins d’importation, ce qui transforme une intention architecturale en un contrôle effectué par les outils CI à chaque demande de fusion.
Une organisation axée sur les fonctionnalités pour React et Next.js
Aucune structure unique n’est adaptée à tous les projets, mais l’organisation par fonctionnalités offre des limites naturelles :
src/
app/
routes/
providers/
layouts/
features/
checkout/
components/
hooks/
services/
state/
types/
products/
components/
hooks/
services/
state/
types/
users/
components/
hooks/
services/
state/
types/
shared/
ui/
hooks/
lib/
types/
La copie de ces noms de dossiers n’est pas l’objectif principal. Ce qui compte, c’est que la propriété des éléments devienne explicite : app relie les routes, les fournisseurs et les layouts entre eux, chaque fonctionnalité possède ses propres composants, hooks, services, état et types, tandis que shared ne contient que ce qui est véritablement générique. Pour une comparaison des alternatives, consultez une comparaison pratique des modèles de structure de dossiers React.
Habitudes à éviter dans un grand frontend
Lorsque vous rejoignez ou maintenez une grande base de code React, soyez vigilant face à ce qui suit :
- Globalisation de l’état par défaut. La plupart des valeurs n’ont jamais besoin de quitter leur composant.
- Généralisation prématurée. Comprenez d’abord les variations avant d’abstraire.
utils.ts. Identifiez le domaine qui doit gérer la logique.De la rédaction de meilleurs codes à la création de systèmes modifiables
Le changement le plus significatif qui accompagne l’expérience est un changement d’objectif plutôt qu’une nouvelle technique. Au début, la question naturelle est de savoir comment écrire un morceau de code donné de manière plus efficace. Plus tard, la question devient comment rendre le système plus facile à modifier pour celui qui le prendra en charge ensuite. Ce changement de perspective redéfinit la manière dont on aborde les composants et les abstractions, la gestion de l’état avec Redux, TypeScript, Next.js, le stylisme, les tests, l’architecture globale et les dépendances. Au lieu de perfectionner des fichiers individuels, on commence à optimiser la capacité du système à évoluer.
La dette technique est un outil, pas un ennemi
On ne peut pas éliminer la dette technique, et essayer de le faire est une erreur. Chaque produit implique des compromis, et beaucoup d’entre eux génèrent de la dette par conception : livrer rapidement, prendre en charge plusieurs exigences métier en même temps, faire fonctionner des navigateurs obsolètes, changer de direction pour le produit ou intégrer une application acquise. Même une excellente décision architecturale peut devenir une dette dès que le système environnant change.
Ainsi, un objectif de « dette zéro » est erroné. L’objectif réaliste est d’avoir une dette que l’équipe comprend, maintient sous contrôle et peut rembourser dès qu’elle commence à affecter la livraison.
Qu’est-ce qui rend réellement difficile le travail sur une application volumineuse ?
Il est utile d’être précis quant aux causes profondes, car ce ne sont rarement celles que les gens imputent :
- De nombreux composants ne sont pas le problème ; ce sont plutôt les relations entre eux, que personne ne parvient à comprendre, qui le sont.
Bref, la dette technique n’est pas un code qui a l’air laid. C’est un code qui rend les modifications inutilement coûteuses, et la meilleure architecture frontend n’est pas celle qui semble parfaite dès le premier jour, mais celle qui permet à l’équipe de progresser rapidement cinq ans plus tard.
Une liste de vérification avant la mise en place d’une nouvelle fonctionnalité
- Y a-t-il une raison réelle pour que cet état soit global ?
- Le backend possède-t-il ces données, ou s’agit-il purement d’un état UI ?
- Le composant assume-t-il une deuxième ou une troisième responsabilité ?
- Cette nouvelle abstraction répond-elle à un besoin réel, ou s’agit-il simplement d’une présentation plus ordonnée ?
- Quelle fonctionnalité est responsable de cette règle métier ?
- Ce code pourrait-il être exécuté sur le serveur de Next.js à la place ?
- Un Effect est-il vraiment nécessaire ici ?
- Cela entraînera-t-il l’ajout d’un autre package à maintenir ?
- Le composant est-il réellement utilisé dans différentes fonctionnalités ?
- Un débutant pourrait-il comprendre ce code sans interroger son auteur ?
Ces questions ont généralement plus de valeur qu’une autre liste de bonnes pratiques React.
Sujets à explorer ensuite
Plusieurs points abordés dans ce guide méritent une analyse plus approfondie en tant que tels :
- Quand Redux est réellement le bon choix, par rapport à l’état local, Context, Redux Toolkit, RTK Query, le traitement dédié de l’état serveur, l’état URL et l’état des formulaires.
- Refactoriser un composant de 1 000 lignes sans perturber la production, à travers une étude de cas pas à pas.
- Si les composants réutilisables rendent une application moins performante, en abordant le problème de l’explosion des props, ainsi que la composition et les systèmes de conception.
- Composants serveur contre composants client dans Next.js, en se concentrant sur les décisions relatives aux limites plutôt que sur la fonctionnalité de
"use client". - TypeScript à grande échelle, y compris l’usage de
any, des génériques, des DTO, des types de domaine et d’API, ainsi que les problèmes de duplication et d’inférence. - Quel élément devrait remplacer le dossier
utilscomme emplacement pour la logique.
Change cost
Bug frequency
Dependency coupling
Component size
State complexity
Build time
Testability
Developer onboarding
Points clés
- La dette frontend est la somme de petites décisions raisonnables ; surveillez la tendance, pas les commits individuels.
- Évaluez la santé du code en fonction du coût et des risques liés aux modifications, et non du fait que l’application fonctionne actuellement.
- Gardez l’état local par défaut, traitez les données serveur comme un problème de mise en cache, et globalisez uniquement ce qui est véritablement transversal.
- Préférez les limites et la direction des dépendances aux noms de dossiers ou aux méthodologies nommées.
- Réduisez progressivement la dette en ciblant les points critiques, plutôt que de miser sur une réécriture complète.