Réagir sans framework métier : les coûts cachés d’un backend en Python
Pourquoi React plus Vite avec un backend non JavaScript élimine le débat autour de Next.js, quels composants de framework vous devez finalement réécrire, et comment gérer les points de jonction.
La plupart des débats sur les frameworks React supposent seulement deux options : rester avec Next.js ou passer à ce qui est actuellement présenté comme son successeur, que ce soit Astro, Remix ou TanStack Start. Une grande partie des applications React utilisées dans la réalité ne suit aucune de ces deux voies. Elles affichent une interface client single-page avec Vite et communiquent avec un backend écrit en Python, Ruby ou PHP qui gère déjà la base de données, les règles métier et l’API. Cet article examine honnêtement cette troisième option : quels sont les reproches faits aux meta-frameworks qui ne s’appliquent tout simplement pas, quelles responsabilités se transforment discrètement dans votre propre code, et comment maintenir un framework développé en interne de manière fiable.
Un exemple concret de la troisième option
Imaginez un système de santé publique qui suit le processus d’enrichissement des aliments. L’interface utilisateur est développée avec React et TypeScript, en utilisant Ant Design comme bibliothèque de composants. Le côté serveur repose sur Frappe, le framework Python qui alimente ERPNext. Frappe définit des modèles de données sous forme de « doctypes », gère la base de données et expose des méthodes Python autorisées en tant qu’extrémités API. En environnement de production, il n’y a ni environnement d’exécution Node.js, ni routeur d’application, ni composants serveur chargés de déterminer quelles parties de l’interface peuvent accéder à la base de données. React s’occupe de l’affichage ; Frappe gère tout le reste.
Pour une équipe dans cette situation, de nombreux arguments les plus virulents contre Next.js sont irrelevants. Le verrouillage d’hébergement sur Vercel n’a pas d’importance lorsque rien n’y est déployé. La lenteur du remplacement en temps réel des modules sous le routeur d’application n’est pas un problème lorsque Vite gère le serveur de développement et recharge les contenus suffisamment rapidement pour que personne ne s’en aperçoive.
La même architecture s’applique chaque fois que React est intégré à Django, Rails, Laravel ou tout autre stack disposant déjà de sa propre logique côté serveur. Cependant, les critiques concernant les meta-frameworks persistent, mais elles proviennent d’une direction différente.
On construit toujours un framework, outil par outil
Éviter un meta-framework ne supprime pas les problèmes qu’il résout. Il les transfère dans votre code, où vous devez les résoudre vous-même, généralement de manière progressive et sans donner de nom à ce que vous créez. Trois exemples tirés de l’application fortification illustrent ce schéma.
Formulaires basés sur des schémas plutôt que sur des conventions de framework
L’équipe a développé un composant DynamicForm qui affiche les formulaires directement à partir des définitions JSON de doctype fournies par le backend. Il lit le schéma, détermine quels champs existent et de quel type ils sont, met en évidence les champs obligatoires, gère ceux dont la visibilité ou la valeur dépend d’un autre champ, et affiche le formulaire complet. Ajouter un nouveau doctype ne nécessite plus d’écrire manuellement un formulaire.
C’est là l’application de la convention plutôt que de la configuration, promesse fondamentale de tout meta-framework, mise en œuvre en interne. Ce n’a pas fonctionné dès la première tentative : le composant a nécessité trois réécritures complètes avant de devenir stable. Un framework aurait absorbé ces itérations à votre place, pour des milliers d’utilisateurs.
Un rapport indiquait la nécessité de charger les lignes par lots au fur et à mesure que l’utilisateur faisait défiler la page. Sans getServerSideProps, ni chargeur de route, ni aucun autre outil de récupération de données autorisé par le framework, la solution a dû être assemblée à partir d’éléments distincts : un IntersectionObserver surveillant un élément indicateur situé près du bas de la liste, un curseur de page suivi manuellement, ainsi qu’un état de chargement dont le comportement devait être conçu entièrement à partir de zéro. Fallait-il afficher un indicateur de chargement en bas de la page, ou bien que les nouvelles lignes apparaissent simplement progressivement ? Sans aucune recommandation du framework à suivre, un seul développeur a dû prendre cette décision au cours d’une journée de travail ordinaire.
Dans une configuration React-plus-Vite, une bibliothèque telle que TanStack Query peut fournir une grande partie de ces fonctionnalités (requêtes infinies, curseurs, indicateurs de chargement), et il vaut la peine d’en évaluer les avantages avant d’écrire soi-même la logique. Le principe de base reste le même : quelqu’un doit choisir et assumer la responsabilité de la couche de données.
Une couche d’adaptation pour des données backend incohérentes
Les listes déroulantes correspondant à une douzaine de types de documents devaient afficher des étiquettes lisible par l’homme, mais chaque type de document nommait son champ d’étiquette différemment. Certains utilisaient label, d’autres title, et un type de document ancien utilisait display_name. Une petite fonction utilitaire mapOptions normalise tous ces formats en un seul schéma, permettant ainsi à chaque composant de liste déroulante d’ignorer ces incohérences.
Nul ne qualifierait cela d’architecture, pourtant c’est précisément le type de normalisation que les conventions de framework imposeraient normalement. Chaque équipe finit par écrire elle-même les structures de base que fournirait un meta-framework. Le seul choix consiste à payer ce prix en acceptant les décisions du framework ou en consacrant son propre temps d’ingénierie.
Lorsque le framework développé en interne devient un problème
Le véritable coût apparaît plus tard. Après quelques années, le composant DynamicForm a été signalé par SonarQube pour sa complexité cognitive excessive. Il ne s’agissait ni d’une erreur de bibliothèque ni d’une incompatibilité de version. C’était simplement une croissance normale du composant : un seul fichier était devenu silencieusement à la fois la couche de routage, la couche de formulaire et la couche de validation de l’application.
Le refactoring a révélé à quel point le code était devenu enchevêtré. En extrayant la logique de validation dans une fonction distincte, on a découvert quatre chemins de code indépendants qui dépendaient d’effets secondaires cachés à l’intérieur du composant. C’est le prix que l’on voit rarement sur la facture lorsqu’on évite un meta-framework : on écrit l’infrastructure, mais on devient également sa seule documentation. Il n’existe aucun outil de suivi d’issues populaire où quelqu’un d’autre aurait rencontré le même problème et publié une solution.
La correction a suivi une décomposition familière :
- Transférer la résolution des dépendances de champ dans un hook personnalisé dédié.
- Placer la validation dans un module séparé qui ne sait rien du mode de rendu du formulaire.
- Laisser le composant lui-même se charger uniquement du rendu.
Après la séparation, le score de complexité a baissé et le composant est devenu plus facile à lire. La leçon générale est que la séparation des préoccupations, imposée par convention dans un meta-framework, doit, dans une stack personnalisée, être appliquée systématiquement par discipline. Aucun outil de détection d’erreurs ne vous avertit que vous construisez mal la couche de données d’un framework. Des habitudes utiles consistent à générer régulièrement des rapports de complexité, à effectuer des revues de code qui se demandent « ce composant devient-il une infrastructure ? », et à créer une documentation interne concise pour tout outil dont dépendent plus d’une poignée d’écrans.
Bug qui surviennent à la jonction entre deux écosystèmes
Un framework comme Next.js gère à la fois le serveur et le client, de sorte que la frontière entre eux est conçue comme un seul système. Lorsque React fonctionne avec un backend Python, c’est vous qui définissez cette frontière, ce qui entraîne une catégorie distincte de bugs.
Un exemple : une erreur MySQL 1292 dont il a fallu presque toute une journée pour en retracer l’origine. Cette erreur indique une valeur de date ou de datetime invalide ou mal formatée. Le formulaire React envoyait les dates selon un format, tandis que l’ORM de Frappe attendait un autre. Ni le frontend ni le backend n’étaient erronés en eux-mêmes ; l’échec provenait d’un problème de compatibilité entre deux écosystèmes qui n’avaient jamais été conçus ensemble, réunis uniquement par des conventions inventées des mois auparavant et en partie oubliées.
Un autre exemple : un validateur de force du mot de passe qui s’active de manière inattendue dans un hook after_insert du backend, déclenché par une structure de données envoyée par le formulaire que personne n’avait testée. Aucun changelog de framework ne couvre cette interaction, car aucun meta-framework ne connaît l’existence de Frappe. Le débogage consiste donc à examiner à la fois son propre code et le code source du framework backend jusqu’à ce que les deux versions de l’histoire concordent.
Quelques mesures pratiques pour se protéger des erreurs liées aux champs de formulaire :
- Sérialisez les dates et les horodatages en un seul endroit du côté client, selon le format spécifié par le backend, et non pour chaque formulaire.
- Considérez la charge utile de la requête comme un contrat : définissez-la en TypeScript et, si possible, validez-la des deux côtés aux points de connexion.
- Rédigez des tests d’intégration qui envoient de vraies charges utiles de formulaire au vrai backend, y compris celles ayant des structures inhabituelles, car les mécanismes côté serveur réagissent aux données auxquelles on ne s’attend pas toujours.
- Enregistrez la charge utile brute en cas d’échec de validation côté backend afin que le déséquilibre soit immédiatement visible.
Si votre back-end est également en JavaScript, un schéma de validation partagé élimine une grande partie de ces difficultés ; consultez la manière de partager un schéma Zod entre une interface frontend React et un back-end Node. Avec un back-end en Python, vous ne pouvez pas partager le code, mais vous pouvez toujours partager le contrat.
Poser correctement la décision
Le débat sur « quel meta-framework choisir » part du principe que le back-end est négociable. Pour de nombreuses équipes, ce n’est pas le cas. Si une application Django, Rails, Laravel ou Frappe existante possède déjà le serveur, React associé à Vite n’est pas un compromis ; c’est l’architecture naturelle. La vraie question devient de savoir quelle partie du travail lié au framework vous êtes prêt à développer et à maintenir vous-même par-dessus un back-end déjà existant.
En échange, vous pouvez choisir le backend qui convient au domaine. Pour un système basé sur des doctypes et des hooks Python, cette liberté est précieuse. Le prix à payer est d’hériter de toutes les conséquences liées à ce choix.
Points clés
- Les arguments concernant l’engagement vis-à-vis de Vercel et les migrations vers le routeur d’applications sont réels, mais ils ne s’appliquent pas aux clients React servis par un backend non JavaScript.
- Même sans framework métier, vous devez quand même créer des couches pour les formulaires, le chargement des données et leur normalisation ; vous les développez vous-même et en assumez la responsabilité à long terme.
- L’infrastructure développée en interne tend à devenir composée de modules complexes ; assurez délibérément une séparation des préoccupations et documentez les outils partagés.
- La frontière entre client et serveur relève de votre responsabilité : centralisez la sérialisation, définites le contrat du payload et testez avec le vrai backend.