Étude de cas · ma propre plateforme
Aissam Portfolio
Le site que vous lisez et ce qui le fait tourner : un site public en trois langues, son back-office, son API et un endpoint MCP pour les clients IA.
- Rôle
- Propriétaire et seul ingénieur, de l’API aux pages
- Année
- 2026
Le défi
Trois applications qui partagent les mêmes types, une seule personne pour les garder en phase, et un client IA qui peut faire ce que fait le back-office.
En chiffres
- 103Outils MCP, aucun écrit à la main : 102 routes du back-office lues dans le registre des routes, plus whoami.
- 193Routes d’API réparties dans 13 modules, chacune nommée par un RAI : son rôle, son module, son action.
- 9Workspaces dans un seul dépôt : trois applications, quatre packages et deux packages d’outillage.
- 333Commits, du premier le 5 septembre 2026 au dernier le 16 octobre.
Un seul dépôt
Changer le contrat, c’est changer les trois applications
L’API, le back-office et le site partagent un seul workspace Bun et Turborepo, parce que les formes qu’ils échangent avaient déjà divergé une fois. La vérification des scopes existait en double : dans le framework de l’API, et recopiée à la main dans le back-office. Les deux concordaient, sans que rien ne les y oblige.
Trois packages portent désormais ce qui passe d’une application à l’autre. @app/contracts contient le type de chaque payload, @app/scopes le vérificateur de permissions, et @app/content les schémas des blocs Markdown et le validateur de documents. @app/contracts à lui seul est importé par 90 fichiers source dans l’API, 88 dans le back-office et 51 dans le site.
Ils sont intégrés au bundle du navigateur et exécutés sous Bun : aucun ne peut importer Hono, les modules natifs de Node ou React. Un changement dans l’un d’eux est un changement du contrat d’échange, vérifié dans l’API, le back-office et le site ensemble.
Dépendances
Qui dépend de quoi
Les flèches vont d’un package vers ce qu’il importe, tel que chaque package.json le déclare. contracts et scopes n’importent rien du tout, content n’ajoute que zod, et core ne tourne que sous Bun, pour l’API seule. Les deux packages d’outillage ne dépendent d’aucun d’eux.
Le back-office
Tout ce que le site montre se modifie ici
Notes, documents de projet, études de cas, page d’accueil, carte du parcours, textes du site, médias et taxonomie se modifient dans une seule application Vite et React 19. L’anglais est la source ; le français et l’arabe s’écrivent à côté.
L’éditeur Markdown vérifie un document avec le même validateur que celui que l’API exécute avant d’enregistrer, et son aperçu est le site lui-même, dans un cadre qui suit le défilement de l’éditeur.
Les écrans sont protégés par le scope dont leurs routes ont besoin, et le back-office le vérifie avec le même code que l’API. Un seul compte détient le rôle sysadmin.
Le parcours d’une note
Une note, de l’éditeur à la page
Étape 1 sur 5
Écrire
Un corps Markdown par langue, dans le back-office. L’anglais est obligatoire ; le français et l’arabe sont à côté et peuvent venir plus tard.
Étape 2 sur 5
Vérifier
Valider lance validateDocument de @app/content dans l’éditeur : les blocs flow, replay et faq doivent être du JSON conforme à leur spec, et une démo inconnue est signalée. Les erreurs de l’API arrivent dans le même panneau.
Étape 3 sur 5
Enregistrer
L’API lance le même validateur avant d’écrire et répond à la première erreur par un 422 avec sa ligne. Une révision de la note telle qu’elle était est gardée ; le nombre de mots et le temps de lecture viennent du validateur.
Étape 4 sur 5
Publier
Un lien d’aperçu signé ouvre le brouillon sur le site pendant 15 minutes. Publier garde une révision, vide les caches des notes et de la taxonomie, et prévient IndexNow pour chaque langue quand une clé est définie.
Étape 5 sur 5
Afficher
Le site lit la note dans l’API et la fait passer par un seul pipeline sur le serveur : GFM, encadrés, intégrations, Shiki pour le code, et les blocs flow, replay, demo et faq changés en composants. Mermaid dessine dans le navigateur.
Architecture
Trois applications, une API, un worker
Le site lit les routes guest, le back-office écrit par les routes sysadmin, et un client IA atteint ces mêmes routes par l’endpoint MCP. Les tâches d’analytics quittent l’API pour un processus worker à elles.
De la route à l’outil
Comment une route du back-office devient un outil MCP
Étape 1 sur 5
Route
Déclarée une fois : méthode, chemin, RAI, rôles, scopes, une description et un schéma zod. La description est ce qu’un modèle lit pour choisir l’outil.
Étape 2 sur 5
Registre
Au démarrage, les routes de chaque module sont aplaties dans un seul registre. Les mêmes déclarations alimentent le cache des permissions, les outils de développement et une collection Postman générée.
Étape 3 sur 5
Projection
Chaque route sysadmin: devient un outil nommé d’après son RAI sans le rôle : sysadmin:cases:update devient cases_update. Exclus : le module users, le nettoyage des médias et les exports CSV.
Étape 4 sur 5
Connexion
Un client IA se connecte par OAuth, avec PKCE et l’enregistrement dynamique des clients, et le propriétaire l’approuve sur un écran du back-office. Un jeton mcp_pat_ collé ne fonctionne que sur cet endpoint.
Étape 5 sur 5
Exécution
Un appel devient un app.fetch sur la vraie route, avec un JWT valable pour un seul aller-retour : la même authentification, les mêmes scopes, la même limite de débit et la même validation que le navigateur. Une création enregistre created_via: mcp.
Rejeu
Un appel MCP, rejoué
Un vrai cases_update, suivi à travers le code. Les durées sont des médianes de 50 appels, mesurées dans le processus sur une machine de développement. Passez à l’échec pour voir un refus du schéma revenir sous une forme que le modèle peut corriger.
Replay
Le site public
Une histoire au fil du défilement, en trois langues
La page d’accueil est une histoire racontée par le défilement : une photo qui se referme en carte, la route sur une carte, une phrase qui s’allume, un anneau d’études de cas en CSS 3D, puis les réalisations, une année de contributions, les notes et un moyen de prendre contact.
Une étude de cas est un enregistrement, pas une page : une liste ordonnée de scènes tirées d’un catalogue de onze, typées dans @app/contracts, validées par l’API et dessinées par un composant par type. Cette page en est une.
L’anglais, le français et l’arabe partagent chaque route, et les pages arabes sont de droite à gauche dès le premier affichage. Les visites sont comptées par l’analytics du site lui-même, qui respecte Do Not Track et Global Privacy Control et atteint l’API par un proxy sur la même origine. Aucun traceur tiers.
Mesure d’audience
Le site compte ses propres visites
Rien n’est collecté tant qu’un visiteur n’a pas accepté le bandeau de consentement, et Do Not Track, Global Privacy Control ou le refus propre au site suffisent chacun à faire taire le traceur. L’identité est un indice aléatoire dans sessionStorage, chiffré avec un sel côté serveur ; l’adresse IP sert une fois, pour un pays, et n’est jamais stockée.
Le site envoie les pages vues, des signaux d’engagement et des événements nommés, comme un téléchargement de CV ou une démo terminée, par lots de 20 au plus via le proxy /a/collect sur la même origine. L’API les écrit sans jamais les modifier, si bien qu’un lot renvoyé ne change rien, et marque les robots, les aperçus et les hôtes hors production pour que les rapports les écartent.
Les sessions et les cumuls quotidiens sont des projections reconstruites par des tâches BullMQ dans le processus worker : un scanner passe chaque minute, et un cumul recalcule une journée entière et est refusé si un plus récent est déjà arrivé. Le back-office les lit, avec objectifs, entonnoirs et parcours. Les événements bruts sont gardés 90 jours, les cumuls 395.
Construit avec
- Bun
- Hono
- MongoDB
- Mongoose
- Redis
- BullMQ
- Zod
- MCP
- Next.js 16
- React 19
- next-intl
- Vite
- TanStack Router
- Tailwind CSS
- Turborepo
- PM2
Où il en est
En ligne sur aissamirhir.com
Une seule configuration PM2 déclare les quatre processus, l’API, le worker d’analytics, le back-office et le site, pour la production et pour le staging. Reste à venir : une boîte de réception des messages dans le back-office ; aujourd’hui, le formulaire transmet à un service externe ou ouvre la messagerie du visiteur.
Parlons du vôtre- Routes de pages dans chaque langue, de l’accueil à cette étude de cas.
- 13
- Écrans de back-office derrière une seule connexion, dont onze pour l’analytics.
- 34