Le jour où nous avons décidé d'arrêter de lutter contre WordPress
J'ai reçu l'appel du CTO de Cannect un mardi après-midi. Leur plateforme de courtage hypothécaire venait d'échouer à un audit de sécurité pour la troisième année consécutive. Les auditeurs signalaient les mêmes problèmes : des dépendances PHP obsolètes, une pile de plugins que personne ne comprenait entièrement, et une intégration API Equifax personnalisée greffée sur WordPress en PHP procédural ayant survécu à trois développeurs différents. La plateforme traitait de vraies demandes hypothécaires - les domiciles des gens étaient en jeu - et les fondations s'étaient discrètement dégradées.
Cet article est le récit complet de ce que nous avons fait ensuite. La planification, les décisions techniques, la migration elle-même, et ce que nous avons fait de travers en cours de route. Si vous faites face à une plateforme WordPress legacy dans un secteur réglementé et vous demandez s'il vaut mieux migrer ou continuer à patcher, voici l'étude de cas dont vous avez besoin.
La situation legacy
La plateforme originale de Cannect était un cas classique de croissance organique qui a mal tourné. Elle a commencé comme un simple site marketing sur WordPress vers 2018. Puis un développeur a ajouté un formulaire de contact. Puis un plugin de calculateur de prêt. Puis quelqu'un a eu besoin de récupérer des données de bureau de crédit, donc il a écrit un plugin PHP personnalisé qui appelait directement l'API Equifax Decision Power. Puis ils ont eu besoin de suivi des demandes, donc une autre table personnalisée a été greffée sur la base de données WordPress à côté de wp_posts et wp_options.
Au moment où je me suis impliqué, le système ressemblait à ceci :
- WordPress 5.9 tournant sur une seule instance EC2 (t3.medium)
- 12 plugins actifs, dont 4 n'avaient pas reçu de mise à jour depuis plus de deux ans
- Plugin PHP personnalisé (~3 200 lignes) gérant l'intégration du bureau de crédit Equifax
- 3 outils de calculateur de prêt construits comme des fonctions PHP séparées basées sur des shortcodes
- Base de données MySQL mélangeant les tables WordPress de base avec 11 tables d'application personnalisées
- Pas d'environnement de staging - les changements allaient directement en production
- Déploiement manuel via SFTP depuis le laptop d'un développeur
- Zéro couverture de tests
L'intégration Equifax était la pièce la plus critique et la plus fragile. Elle gérait les tirages de crédit pour la pré-qualification hypothécaire, ce qui signifie qu'elle touchait des données financières sensibles dans un fichier PHP qui vivait dans le répertoire wp-content/plugins aux côtés d'un plugin de coupons et d'un outil SEO cassé.
La performance était un autre problème. Les temps de chargement des pages de calculateur atteignaient régulièrement 4-6 secondes. Le serveur grimpait à plus de 90 % de CPU pendant les heures ouvrées lorsque plusieurs courtiers exécutaient des demandes simultanées. Ils n'avaient pas de système de file d'attente - chaque soumission de calculateur et chaque appel API Equifax s'exécutait de manière synchrone dans la même requête.
Pourquoi nous avons décidé de migrer
Je pousse toujours un premier refus sur les projets de migration. Les réécritures sont coûteuses, risquées et faciles à sous-estimer. J'ai dit à l'équipe de justifier clairement le besoin avant que j'accepte d'en planifier une. Voici ce qu'ils m'ont présenté.
Posture de sécurité. La plateforme stockait des numéros d'assurance sociale partiels, des données de vérification de revenus et des scores de crédit. La conformité PCI-DSS était prévue pour l'année suivante. WordPress avec une architecture de plugins personnalisés n'est pas l'endroit idéal quand vous avez besoin d'une certification SOC 2 ou PCI. La surface d'attaque - exécution PHP, vulnérabilités de plugins, pas de gestion des secrets - était trop large pour être sécurisée sans essentiellement reconstruire l'application de toute façon.
Vélocité de développement. L'intégration d'un nouveau développeur prenait trois semaines parce que la base de code n'avait ni documentation, ni tests, et le système de plugins nécessitait de comprendre les mécanismes internes de WordPress avant de pouvoir modifier quoi que ce soit lié à la logique métier. Les pull requests prenaient des jours parce que personne ne pouvait évaluer avec confiance le périmètre d'impact d'un changement.
Plafond de performance. Ils avaient déjà fait du scale vertical deux fois. L'architecture serveur unique n'avait pas de chemin de scale horizontal sans un travail significatif. Pendant les périodes de pointe de demandes, les sessions des courtiers expiraient.
Le coût de rester. C'est l'argument qui a fait mouche. J'ai demandé au CTO d'estimer les heures d'ingénierie passées chaque trimestre en maintenance, patches de sécurité et gestion de crise. Le chiffre était d'environ 60-80 heures par trimestre, soit approximativement 18 000-24 000 $ CAD en temps développeur, pour une plateforme qui avait encore des constats d'audit de sécurité ouverts année après année.
La migration n'était pas bon marché. Mais rester ne l'était pas non plus.
Planifier la migration
Avant d'écrire une seule ligne de nouveau code, nous avons passé trois semaines sur un audit. Je tiens à être précis sur ce que couvrait cet audit, parce que sauter cette étape est la façon dont les migrations échouent.
Inventorier tout ce qui existe. Nous avons catalogué chaque endpoint, chaque formulaire, chaque hook et filtre WordPress que les plugins personnalisés utilisaient, chaque table de base de données et chaque appel API externe. Nous avons utilisé une combinaison d'analyse statique, d'introspection de base de données et simplement de lecture du code. L'inventaire a révélé deux intégrations que personne dans l'équipe actuelle ne savait actives - un fournisseur SMS tiers et un ancien webhook Slack qui se déclenchait à la soumission des demandes.
Identifier ce qui est réellement utilisé. L'installation WordPress avait des fonctionnalités qui avaient été construites puis abandonnées. Nous avons croisé les logs d'accès avec l'inventaire d'URL et découvert qu'environ 30 % des pages de shortcodes personnalisés n'avaient reçu aucun trafic au cours des six derniers mois. Celles-ci ont été supprimées entièrement.
Comprendre le modèle de données. Les tables d'application personnalisées étaient mal normalisées et contenaient plusieurs colonnes qui existaient "au cas où" et étaient toujours null. Avant de concevoir le nouveau schéma, nous avons tracé chaque champ à travers la base de code pour comprendre ce qui était réellement écrit, lu et utilisé en aval.
Cartographier l'intégration Equifax précisément. Cela nécessitait le plus de soin. L'API Equifax Decision Power a des exigences strictes autour de la gestion des données, la sécurité de transmission et le stockage des réponses. Nous avons documenté chaque type de requête que le plugin existant effectuait, les champs de réponse stockés et la politique de rétention (qui s'est avérée être "stocké indéfiniment" - quelque chose que nous avons corrigé dans la nouvelle conception).
Avec l'audit terminé, nous avons décidé d'une approche de fonctionnement parallèle par phases. La nouvelle plateforme tournerait en parallèle de WordPress pendant environ six semaines, avec les courtiers migrés progressivement par compte. C'était plus coûteux qu'une bascule sèche mais critique dans un contexte réglementé où un déploiement raté a des conséquences réelles pour de vraies demandes hypothécaires.
Choisir le nouveau stack
La fintech n'est pas l'endroit pour expérimenter avec des frameworks bleeding-edge. Le stack devait être éprouvé en production, bien documenté et facile à recruter.
Frontend : Next.js 14 (App Router) Le portail courtier nécessitait du rendu côté serveur pour l'amélioration de performance du chargement initial et le SEO pour les pages de calculateur publiques. Next.js avec l'App Router nous a donné les React Server Components pour les parties statiques et les composants client uniquement là où l'interactivité était nécessaire.
Couche API : Node.js avec tRPC Nous avons choisi tRPC plutôt que REST parce que l'équipe était TypeScript-first, et avoir une sécurité de type de bout en bout entre le frontend Next.js et la couche API éliminait toute une classe de bugs qui avaient gangréné la base de code PHP. Les contrats de types imposaient la documentation par défaut.
Base de données : PostgreSQL sur RDS (Aurora Serverless v2) PostgreSQL était le choix évident - le modèle de données était relationnel, nous avions besoin de contraintes de clés étrangères appropriées (absentes dans la configuration WordPress), et les données financières exigeaient des garanties ACID. Aurora Serverless v2 nous donnait l'auto-scaling sans le coût opérationnel de gérer manuellement des répliques de lecture.
Jobs en arrière-plan : BullMQ sur Redis Chaque appel API Equifax a été déplacé dans une file d'attente asynchrone. Un tirage de crédit synchrone qui pouvait échouer ou expirer n'avait rien à faire dans une requête web. BullMQ sur ElastiCache Redis nous a donné les retries de jobs, les files prioritaires et la visibilité sur l'état des jobs que l'ancienne implémentation PHP n'avait absolument pas.
Infrastructure : AWS avec CDK Toute l'infrastructure en tant que code. Plus d'accès SSH aux serveurs de production, plus de déploiements SFTP. CDK en TypeScript signifiait que l'infrastructure était typée aux côtés du code applicatif.
Chronologie de la migration
Plateforme Cannect - 14 semaines au total
Audit et planification
Inventaire, modèle de données, cartographie API Equifax, décisions d'architecture
Base de données et couche API
Conception du schéma, scripts de migration, mise en place du routeur tRPC, système d'authentification
Frontend et intégrations
Portail Next.js, calculateurs de prêts, worker de file Equifax, intégration SMS
Fonctionnement parallèle
20 % des courtiers sur la nouvelle plateforme, synchronisation des données depuis le legacy, résolution de problèmes
Déploiement complet
Courtiers restants migrés, WordPress décommissionné, monitoring renforcé
Le processus de migration
Migration de la base de données
La migration des données était la partie sur laquelle j'étais le plus prudent. Nous avions 11 tables personnalisées, 6 ans de données applicatives et zéro confiance dans l'intégrité des enregistrements existants (l'ancien système n'avait pas de contraintes de clés étrangères, donc les enregistrements orphelins étaient courants).
La migration s'est déroulée en trois phases. Premièrement, une synchronisation en lecture seule : nous avons écrit un script Node.js qui tirait en continu depuis la base MySQL de WordPress et écrivait des enregistrements normalisés dans le nouveau schéma PostgreSQL, avec une colonne origin_legacy_id pour tracer chaque enregistrement jusqu'à sa source. Cela a tourné en arrière-plan pendant deux semaines avant qu'aucun courtier ne touche le nouveau système.
Deuxièmement, la résolution de conflits : nous avons identifié ~1 400 enregistrements de demandes qui échouaient à la validation contre le nouveau schéma - principalement des nulls dans des champs que le nouveau modèle exigeait, et une poignée d'enregistrements dupliqués créés par une condition de concurrence dans l'ancienne logique de soumission. Chaque catégorie a reçu une règle de résolution.
Troisièmement, la bascule : quand un courtier était migré vers la nouvelle plateforme, son compte devenait la source de vérité dans PostgreSQL. Une fine couche de synchronisation continuait d'écrire dans les tables MySQL legacy pendant la durée du fonctionnement parallèle, afin que les vues admin WordPress restent exactes pour l'équipe opérationnelle jusqu'à ce que nous soyons prêts à les désactiver.
Couche API
Nous avons construit l'API tRPC dans un monorepo aux côtés du frontend Next.js. La structure du routeur correspondait étroitement aux objets du domaine : applications, brokers, calculators, creditPulls. Chaque routeur avait une validation d'entrée explicite utilisant des schémas Zod - quelque chose que la base de code PHP n'avait jamais fait du tout.
Une décision précoce qui a payé : nous avons gardé l'API tRPC appelable depuis l'extérieur de Next.js en exposant un adaptateur HTTP. Cela signifiait que de futurs clients mobiles ou intégrations tierces pourraient utiliser la même API validée plutôt que de la contourner.
Frontend
Les calculateurs de prêt publics étaient les pages à plus fort trafic et la victoire la plus facile. Les anciens shortcodes PHP rendaient côté serveur à chaque requête, incluant le bootstrap complet de WordPress. Les déplacer vers des React Server Components avec des coquilles statiques pré-rendues a réduit le time-to-interactive de 4,2 secondes à moins de 800 ms - avant toute autre optimisation.
Le portail courtier a nécessité plus de soin. Les courtiers avaient une forte mémoire musculaire pour l'ancienne interface, et nous avions été prévenus que toute régression visuelle générerait des plaintes immédiates. Nous avons fait une revue côte à côte de chaque écran avec deux courtiers pendant la phase de fonctionnement parallèle, et nous avons itéré sur trois écrans qui causaient des frictions avant de déployer pour l'ensemble des courtiers.
L'intégration API Equifax
C'était la partie la plus techniquement exigeante de la migration. L'API Equifax Decision Power n'est pas une intégration indulgente - elle a des limites de débit strictes, des règles de validation des charges utiles et des exigences de piste d'audit que l'ancien plugin PHP gérait de manière inconsistante (parfois pas du tout).
La nouvelle intégration comportait quatre composants :
1. Un client Equifax typé. Toute la communication API centralisée dans un seul module avec des types TypeScript générés à partir du schéma XML Equifax. Plus de PHP procédural faisant des appels HTTP avec des paramètres curl bruts.
2. Une file de jobs BullMQ. Les demandes de tirage de crédit entraient dans une file prioritaire. Les courtiers voyaient un état immédiat "demande reçue" dans l'interface pendant que le job se traitait en asynchrone. Si l'API Equifax retournait un 429 ou un 5xx, le job réessayait avec un backoff exponentiel plutôt que d'échouer silencieusement comme la version PHP le faisait souvent.
3. Un journal d'audit. Chaque requête et réponse Equifax était journalisée dans une table d'audit immuable dans PostgreSQL - timestamp de la requête, ID du courtier, ID de la demande, code de réponse et un hash de la charge utile de la requête. Cette piste d'audit était quelque chose que les auditeurs de sécurité avaient signalé comme manquant pendant deux années consécutives.
4. L'application de la politique de rétention des données. L'ancien système stockait les réponses de crédit complètes indéfiniment. Le nouveau système appliquait une politique de rétention de 90 jours via un job planifié, ne conservant que les champs dérivés nécessaires au traitement continu des demandes. Cela réduisait significativement la fenêtre d'exposition pour les données sensibles.
L'approche par file d'attente a révélé quelque chose d'intéressant : l'ancienne intégration PHP avait un taux d'échec silencieux d'environ 12 % - des requêtes qui erraient sans journalisation ni notification utilisateur. Nous avons découvert cela en rejouant les requêtes historiques contre la nouvelle file et en comparant les résultats. Ces 12 % représentaient de vraies demandes où les courtiers avaient manuellement relancé Equifax par téléphone sans savoir que c'était une défaillance de la plateforme.
Stratégie de tests et de déploiement
Nous avons abordé les tests par couches.
Les tests unitaires couvraient le client Equifax, la logique de calcul de prêts et tous les handlers de routeur tRPC. Ils ont été écrits avant l'implémentation avec Jest. Les tests de calculateur étaient particulièrement précieux parce que l'ancienne implémentation PHP avait trois fonctions de calcul différentes qui produisaient des résultats légèrement différents pour les mêmes entrées - un bug qui était passé inaperçu faute de tests pour les comparer.
Les tests d'intégration couvraient les scripts de migration de base de données et le worker de file d'attente. Nous utilisions une instance PostgreSQL de test alimentée avec des snapshots de données de production anonymisées.
Les tests de bout en bout avec Playwright couvraient les cinq flux critiques des courtiers : connexion, création de demande, soumission de calculateur, demande de tirage de crédit et mise à jour du statut de demande. Ils tournaient sur chaque pull request contre un environnement de staging qui reflétait la production.
Pour le déploiement, nous avons utilisé un système de feature flags (LaunchDarkly) pour contrôler quels comptes courtiers étaient dirigés vers la nouvelle plateforme. Nous avons commencé avec 5 comptes de test internes, puis 20 % des courtiers (les plus à l'aise techniquement, sélectionnés délibérément), puis 60 %, puis 100 %. Chaque phase avait une fenêtre d'observation de deux jours avant de passer à la suivante.
Le fonctionnement parallèle a révélé trois problèmes que nous n'aurions pas détectés autrement : un bug de gestion de fuseau horaire dans les timestamps des demandes (l'ancien système stockait tout en heure de l'Est sans marqueur de fuseau ; le nouveau utilisait UTC), un champ manquant dans les emails de notification aux courtiers, et une régression de performance dans la vue liste des demandes sous filtrage intensif. Les trois ont été corrigés avant le déploiement complet.
Avant et après : les chiffres
Comparaison de performance de la plateforme
WordPress (legacy) vs. Next.js + Node.js (nouveau)
| Métrique | WordPress | Nouveau stack | Évolution |
|---|---|---|---|
| Chargement page calculateur (p50) | 4,2 s | 0,78 s | -81 % |
| Temps de soumission de demande | 3,1 s | 0,42 s | -86 % |
| Taux d'échec silencieux Equifax | 12 % | 0,3 % | -97 % |
| Utilisation CPU en pic | 91 % | 23 % | -75 % |
| Coût infra mensuel (AWS) | 340 $ | 290 $ | -15 % |
| Heures maintenance dev (par trimestre) | ~70 h | ~12 h | -83 % |
| Temps d'intégration nouveau développeur | 3 semaines | 3 jours | -93 % |
| Constats d'audit sécurité ouverts | 7 | 0 | -100 % |
Les économies en coûts d'infrastructure étaient plus faibles que le titre, mais c'était attendu - nous avons ajouté Redis managé, Aurora Serverless et un pipeline CI/CD approprié. Le vrai retour financier est venu de la réduction de la charge de maintenance : 58 heures par trimestre récupérées à 150 $/heure, c'est plus de 34 000 $ CAD annuellement en capacité d'ingénierie redirigée vers le développement produit.
Leçons apprises
L'audit n'est pas optionnel. Chaque fois que j'ai sauté ou abrégé un audit de base de code sur un projet de migration, cela a coûté plus de temps en aval que l'audit n'en aurait pris. Vous ne pouvez pas estimer précisément la portée d'une migration sans savoir ce qui existe réellement. Les deux intégrations mystères de Cannect - le fournisseur SMS et le webhook Slack - auraient cassé la production si nous les avions manquées.
Asynchrone d'abord pour les API externes. Tout appel à une API tierce qui affecte un état visible par l'utilisateur devrait être mis en file d'attente, pas synchrone. L'intégration Equifax tournant dans le processus d'une requête web PHP était toujours une bombe à retardement. La déplacer dans une file d'attente a amélioré la fiabilité, la visibilité et le comportement de retry simultanément.
Le fonctionnement parallèle ajoute du temps mais réduit le risque. Nous avons failli sauter le fonctionnement parallèle pour gagner deux semaines. Avec le recul, le bug de fuseau horaire et la régression des emails de notification détectés pendant le fonctionnement parallèle auraient causé des plaintes significatives des courtiers et potentiellement corrompu des enregistrements de demandes si nous avions fait une bascule sèche. Dans les industries réglementées, le fonctionnement parallèle est une assurance.
La dette de qualité de données arrive à échéance au moment de la migration. Les ~1 400 enregistrements qui ont échoué à la validation de migration n'étaient pas un nouveau problème - c'étaient des années de problèmes de qualité de données que l'ancien système avait silencieusement acceptés. Une migration vous force à confronter cette dette. Budgétez-la.
La partie la plus difficile n'est pas la technologie. La partie la plus difficile était de gérer la transition pour les courtiers qui utilisaient la même interface depuis des années. Trois écrans ont nécessité des itérations après le fonctionnement parallèle parce que nous avions optimisé pour ce que nous pensions être une meilleure UX sans tester adéquatement avec les vrais utilisateurs. Intégrez des sessions de feedback courtier plus tôt dans le plan.
La sécurité de type se rentabilise. Nous avons trouvé six erreurs de calcul pendant la migration tRPC + Zod qui existaient dans la base de code PHP sans être détectées - des cas limites où le calculateur de prêt retournait des résultats incorrects pour des combinaisons d'entrées spécifiques. TypeScript et la validation de schéma à l'exécution attrapent des catégories entières de bugs avant qu'ils n'atteignent la production.
Quand NE PAS migrer
Je tiens à être direct sur ce point, car j'ai vu des clients se convaincre de réécritures qui n'étaient pas justifiées.
Ne migrez pas si la plateforme fonctionne correctement et le problème principal est esthétique. Un site WordPress qui charge en deux secondes, a une gestion propre des plugins et est maintenu par des gens qui le connaissent bien n'a pas besoin d'une réécriture pour satisfaire la préférence de quelqu'un pour React.
Ne migrez pas si votre équipe ne peut pas maintenir le nouveau stack. Cannect avait de l'expérience TypeScript dans son équipe et un pipeline de recrutement qui favorisait les développeurs Node.js. Si leur équipe avait été uniquement PHP et ne prévoyait pas de changer, introduire Next.js et tRPC aurait échangé un problème de maintenance contre un pire.
Ne migrez pas sans sponsoring exécutif et un budget réaliste. Cette migration a coûté à Cannect approximativement 65 000 $ CAD en temps d'ingénierie sur 14 semaines. Le ROI était clair et la période de retour était sous deux ans, mais les projets de cette envergure stagnent ou échouent quand la direction s'attend à un travail de nettoyage à 10 000 $.
Ne migrez pas sous pression de délai. Si la raison de la migration est une deadline de conformité ferme dans six semaines, une réécriture complète est le mauvais outil. Un renforcement ciblé du système existant fait gagner du temps. Faites la migration correctement, sur un calendrier raisonnable, ou ne la faites pas.
Envisagez l'étranglement incrémental à la place. Le pattern Strangler Fig - remplacer progressivement des morceaux du système legacy par de nouveaux services, en routant le trafic de manière incrémentale - est souvent moins risqué qu'une réécriture parallèle complète. Pour Cannect, le modèle de données était trop étroitement couplé au schéma WordPress pour étrangler efficacement, donc une construction parallèle avait plus de sens. Mais pour les systèmes avec des frontières plus propres, la migration incrémentale réduit le rayon d'explosion de toute défaillance individuelle.
Où en est Cannect maintenant
Huit mois après la décommission de la plateforme WordPress, Cannect a réussi son audit de sécurité pour la première fois. Leur équipe de développement a livré quatre nouvelles fonctionnalités au premier trimestre sur la nouvelle plateforme - plus que ce qu'ils avaient livré l'année précédente. La performance du portail courtier n'a pas été soulevée comme un problème depuis le lancement.
Le journal d'audit Equifax, qui avait été un constat récurrent pendant trois ans, passe désormais automatiquement. Le job de rétention tourne selon le calendrier. Le worker de file d'attente traite les demandes de tirage de crédit avec un taux de réussite de 99,7 %, et quand une défaillance survient, le courtier voit un message d'erreur significatif et l'équipe opérationnelle reçoit une notification Slack avec le contexte complet.
Ce n'a pas été un projet sans douleur. Le fonctionnement parallèle s'est prolongé d'une semaine, le bug de fuseau horaire a nécessité un backfill de données, et nous avons pris des décisions d'interface que nous avons dû revoir. Mais le système que Cannect fait tourner aujourd'hui est un système que leur équipe peut comprendre, étendre et auquel elle peut faire confiance.
C'est ce qu'une migration devrait délivrer - pas juste de la nouvelle technologie, mais une plateforme avec laquelle vous pouvez réellement travailler.
Vous traversez une décision similaire ? Si vous avez une plateforme legacy en fintech ou dans un autre secteur réglementé et essayez de déterminer s'il faut migrer, moderniser en place ou continuer à patcher, contactez-moi via le formulaire. Je serai heureux de discuter des compromis pour votre situation spécifique avant que vous ne vous engagiez dans quoi que ce soit.