Architecture frontend · Migration incrémentale · 2026 · mesurée de juin à septembre
Mener une migration progressive de Razor vers React sans arrêter un produit en production
Le produit était une plateforme ASP.NET Core multi-tenant en production dont l'interface s'était étendue entre MVC, Razor, JavaScript/jQuery et quelques composants Blazor. Une réécriture n'était pas réaliste, alors j'ai piloté la direction frontend d'une migration incrémentale vers React + TypeScript : navigation hybride, routes détenues par les features, un BFF same-origin, une structure de page réutilisable et des garde-fous de build, pour que l'interface legacy et React coexistent jusqu'à ce que chaque domaine métier puisse être remplacé sans risque.
- Mon rôle
- Architecture frontend et direction de l'implémentation : les miennes. Les features métier sur la plateforme : toute l'équipe.
- Stack
- React
- TypeScript
- React Router
- Redux Toolkit
- RTK Query
- Vite (library mode)
- ASP.NET Core
- BFF
En bref
- Les routes React réelles sont passées de 10 à 112 entre la baseline de juin et le snapshot de septembre
- Artefact JavaScript d'entrée de 1 764 861 à 63 249 octets (le chunk d'entrée uniquement)
- 108 routes réelles sur la ligne de staging, dont 65 explicitement mappées vers des URL legacy
Contexte
Le produit était une plateforme ASP.NET Core multi-tenant déjà utilisée, dont l'interface avait grandi entre MVC, Razor, JavaScript/jQuery et quelques composants Blazor. Une réécriture complète n'était pas réaliste : les clients dépendaient de l'application existante, le développement des fonctionnalités devait continuer, et beaucoup de zones legacy étaient couplées à l'authentification, au contexte tenant et aux flux opérationnels.
Les contraintes étaient concrètes. Le produit devait rester utilisable tout du long. Les navigations legacy et React devaient coexister sans casser les autorisations ni les deep links. Le navigateur ne devait jamais recevoir le bearer token de l'API en aval. Les nouvelles features avaient besoin de patterns répétables pour l'état, les contrats API, le layout et la gestion d'erreurs. Et chaque changement passait par testing et staging avant la production : il devait donc être compatible et facile à annuler.
Ce que j'ai fait
- J'ai monté React et TypeScript sous /app dans l'hôte ASP.NET existant, avec React Router utilisant /app comme basename tandis qu'ASP.NET gardait la session et l'intégration côté serveur.
- J'ai mis en place le flux navigateur → BFF same-origin : le JWT de l'API reste dans la session côté serveur, React reçoit un snapshot d'authentification et utilise une protection CSRF pour les mutations. Redux Toolkit et RTK Query sont devenus le modèle partagé pour l'état serveur, le cache et l'injection d'endpoints par feature.
- J'ai conçu le modèle de navigation hybride — page legacy, résolution automatique ou route React explicite — avec des utilitaires partagés qui préservent query strings et hashes et gèrent les destinations SPA, pleine page et externes via une seule interface.
- J'ai déplacé la découverte des routes vers des définitions détenues par les features via import.meta.glob, avec contrôle des doublons, pour qu'une feature ordinaire ne modifie plus un registre central.
- J'ai construit PageShell, un layout réutilisable avec en-tête, sous-en-tête optionnel et corps défilant : sur les pages opérationnelles desktop, actions et filtres restent visibles pendant que la zone de données défile ; sur petits écrans, retour au défilement naturel de la page.
- J'ai implémenté le découpage du code par route et par vendor dans le build Vite en mode librairie tout en préservant le pont ASP.NET, gardé une feuille de style unique (un CSS découpé pouvait rester orphelin dans cette configuration) et ajouté un vérificateur de bundle qui fait échouer le build si un chunk référencé manque.
Décisions
Incrémental, dans le même hôte
Pas de seconde application ni de bascule big-bang. React vit sous /app dans l'hôte ASP.NET existant : chaque domaine métier migre quand c'est sûr et la page legacy reste disponible jusque-là.
Le token reste côté serveur
Le BFF conserve le JWT de l'API dans la session côté serveur ; le navigateur ne reçoit jamais qu'un snapshot d'authentification et un token CSRF pour les mutations.
Des routes détenues par les features
Les définitions de routes vivent à côté de la feature et sont découvertes au build. Le registre central était devenu un point chaud de merge et un endroit facile pour oublier une entrée.
JavaScript découpé, une seule feuille de style
Découpage du code pour les routes et les vendors, mais un bundle CSS unifié : en mode librairie, une feuille de style découpée pouvait ne jamais être chargée. Le vérificateur attrape un chunk manquant à la release plutôt qu'en production.
La difficulté concrète
À mesure que la surface React grandissait, le BFF et le routage sont devenus la principale source de risque d'intégration : d'un côté une grande surface centrale d'endpoints, un enregistrement des dépendances qui pouvait diverger entre production et tests, des en-têtes HTTP partagés et des écarts de contrat client/serveur ; de l'autre, des résolveurs dupliqués et des destinations incohérentes.
Stabiliser a signifié des endpoints, services et DTO par feature avec une composition d'injection de dépendances partagée, des en-têtes HTTP par requête, des contrôles d'inventaire client/serveur, une vérification des contrats Swagger, une navigation centralisée et des contrôles de bundle à la release. J'ai travaillé avec la personne qui promouvait les branches de release et je suis resté impliqué en staging jusqu'à ce que les échecs frontend atteignent un niveau opérationnel stable.
Résultat
- Entre la baseline vérifiée de juin et le snapshot de septembre, les routes React réelles sont passées de 10 à 112 sur HEAD ; la ligne de staging examinée contenait 108 routes réelles, hors 42 placeholders et deux routes réservées au développement.
- Sur les 108 routes de staging, 65 étaient explicitement mappées vers des URL legacy, 16 déclarées comme routes React natives et 27 étaient des chemins supplémentaires de détail, création, édition ou sans mapping.
- Les pages React promues en staging étaient opérationnelles selon l'observation directe pendant le travail ; le dépôt contenait 317 fichiers de tests frontend et le tracker de migration a passé 16 tests sur 16.
- Le résultat durable est une plateforme de migration : routes détenues par les features, navigation partagée, structure de page réutilisable, frontières BFF, patterns d'état cohérents et garde-fous de release qui ont permis au travail React de s'étendre pendant que le produit legacy restait disponible.
Les chiffres, avec leurs limites
- Routes React réelles sur HEADAvant10Après112baseline de juin → snapshot de septembre 2026
- Routes réelles sur la ligne de staging108hors 42 placeholders et 2 routes réservées au développement
- Routes de staging mappées vers une URL legacy65 sur 10816 routes React natives, 27 chemins de détail/création/édition ou sans mapping
- Artefact JavaScript d'entréeAvant1 764 861 octetsAprès63 249 octetsle chunk d'entrée uniquement, pas le JavaScript total
- Fichiers de tests frontend · tracker de migration317 fichiers · 16 / 16 testscomptages historiques, septembre 2026
Ce que ces chiffres ne disent pas
- Une route n'est pas une feature métier complète, et le nombre de routes ne dit rien de la part du produit legacy déjà migrée.
- La réduction du chunk d'entrée n'est pas une réduction du JavaScript total transféré ni du temps de chargement ; aucun des deux n'a été mesuré.
- Le fonctionnement en staging est une observation d'ingénierie, pas un smoke test route par route, une activation client ou une adoption en production. Ces données n'ont pas été conservées comme métriques vérifiables, c'est pourquoi je ne présente pas ce travail comme une migration terminée.
Ce que ça prouve
Je sais concevoir un chemin de migration qui laisse une nouvelle stack grandir dans un produit legacy en production — avec des frontières de sécurité, des patterns répétables et des garde-fous de release — et rester responsable jusqu'à la stabilisation.
Attribution
J'ai piloté l'architecture frontend et la direction de l'implémentation, avec l'assistance de l'IA pour l'analyse du dépôt et l'implémentation, tout en gardant les décisions, la revue et la préparation des releases. Les features métier construites sur la plateforme appartiennent à toute l'équipe, et les choix de librairie UI ont été des décisions partagées.