Ce portfolio

Le site que vous êtes en train de lire. Une application Next.js en App Router où chaque chaîne de caractères vit dans un dictionnaire unique, anglais et français, plutôt que dans le balisage ; où la grille de projets, ces pages de détail et leurs métadonnées sortent toutes d’un seul fichier de données ; et où le formulaire de contact est la seule chose qui s’adresse à un serveur — validée, limitée par IP, écrite en MySQL et poussée sur mon téléphone via Telegram. Déployé comme serveur Node autonome sur un hébergement mutualisé cPanel.
Résultats
- Routes issues d’une seule coquille App Router
- 10Routes issues d’une seule coquille App Router
- Langues, un dictionnaire, aucun texte en dur
- 2Langues, un dictionnaire, aucun texte en dur
- Requête que le site émet à l’exécution
- 1Requête que le site émet à l’exécution
- Messages par heure et par IP, vérifiés en SQL
- 5Messages par heure et par IP, vérifiés en SQL
Chiffres relevés dans le dépôt le 11 août 2026 — environ 4 900 lignes de TypeScript réparties sur 36 fichiers, dont 1 177 pour les deux dictionnaires.
Démonstration

Dans le site
Démodélisé, pas rhabillé
Les routes en double de src/pages ont été déplacées vers src/views, le contenu remplacé d’après mon propre CV, et chaque chaîne restante versée au dictionnaire.
Chaque chaîne dans un dictionnaire
L’anglais et le français cohabitent dans un seul fichier, environ un quart du code. Une vue qui code un mot en dur est un bug.
Une langue par défaut, prérendue
Le fournisseur rend le français pour coller au HTML prérendu, puis applique un choix enregistré après le montage — une préférence appliquée au premier rendu casserait l’hydratation.
Les projets sont des données
Un tableau alimente la grille, la page de détail, ses paramètres statiques et ses métadonnées ; les champs facultatifs décident des sections qui existent.
Enregistrer d’abord, notifier ensuite
La route de contact stocke le message avant d’alerter Telegram, limite à cinq par heure et par IP, et marque la ligne pour qu’une notification échouée se retrouve.
Un vrai serveur sur un mutualisé
Un build autonome sous le Passenger de cPanel : middleware, route d’API et MySQL fonctionnent sur une offre Namecheap — identifiants tenus hors du build.
Comment ça marche
Celui-ci aussi a commencé chez quelqu’un d’autre : un modèle de portfolio Next.js sous licence MIT, retenu pour sa coquille sombre et son travail d’animation plutôt que pour son contenu. De ce contenu, presque rien n’a survécu, et la structure a mis un moment à suivre. Le modèle rangeait ses écrans dans src/pages, ce qui publiait chaque route deux fois — /About autant que /about — : les écrans ont donc déménagé dans src/views, où ce sont de simples composants que les pages de l’App Router rendent, et les anciennes URL avec majuscule sont désormais redirigées en 308 par le middleware. Impossible de les traiter dans next.config.js, dont les sources sont comparées sans tenir compte de la casse : /about se redirigerait vers lui-même indéfiniment.
La règle autour de laquelle tout le reste est construit : aucune vue ne contient un mot de texte. Chaque chaîne — navigation, titres, l’intégralité de ces descriptions de projets — se trouve dans src/i18n/dictionaries.ts, anglais et français côte à côte, et une vue la lit à travers un contexte. Ce fichier fait aujourd’hui environ 1 200 lignes, soit un quart du code, et c’est la raison pour laquelle le sélecteur de langue est un simple bouton et non un second site. Le fournisseur rend d’abord la langue par défaut — le français, qui est aussi celle dans laquelle les pages sont prérendues, car le premier rendu client doit correspondre exactement à ce HTML ; partir d’une préférence enregistrée créerait une erreur d’hydratation —, puis applique un choix enregistré dans un effet. Il met aussi à jour document.documentElement.lang, car c’est de cet attribut qu’un lecteur d’écran tire sa prononciation.
Les projets sont des données, pas des pages. Un seul tableau, dans src/data/projects.ts, porte le slug, les images, les tags et les liens de chaque entrée : la grille de cartes le parcourt, la route de détail en tire ses paramètres statiques, et generateMetadata lit la même entrée pour produire le titre, la description, l’URL canonique et l’image Open Graph — en anglais, délibérément : les métadonnées sont rendues côté serveur alors que le sélecteur de langue vit côté client, elles ne peuvent donc pas suivre le lecteur comme le fait la page. Ce sont les champs facultatifs d’une entrée qui décident de la forme de sa page : donnez-lui une capture et elle gagne une section démonstration, un schéma et elle gagne une section architecture. Ajouter un projet, c’est un objet et un bloc de texte ; la page, ses métadonnées et son URL suivent.
Une seule route de tout le site n’est pas statique, et c’est celle qui compte — le formulaire de contact. Elle valide, puis compte les messages récents venus de la même IP grâce à un index et refuse le sixième dans l’heure : sans cela, quiconque trouve le point d’entrée peut faire sonner mon téléphone autant qu’il le souhaite. L’ordre qui suit est voulu : le message est d’abord écrit en MySQL et seulement ensuite envoyé à Telegram, pour qu’un jeton périmé ou une panne de Telegram ne transforme pas une demande enregistrée en erreur affichée à l’expéditeur. Le chemin de notification ne lève jamais d’exception, et la ligne conserve un indicateur telegram_sent afin que les notifications échouées se retrouvent plus tard au lieu de disparaître en silence. Le corps du message est échappé pour le mode HTML de Telegram plutôt que pour MarkdownV2 — trois caractères à échapper au lieu de dix-huit, sur un texte saisi par un inconnu.
L’hébergement est volontairement sans prestige. next build produit un serveur autonome qui tourne sous le gestionnaire Node.js de cPanel, sur un mutualisé Namecheap : le middleware, la route d’API et le pool MySQL sont donc du vrai code serveur, et non un export statique amputé de ses parties intéressantes ; les identifiants de la base et de Telegram sont des variables d’environnement dans ce panneau, jamais des fichiers dans le build. Autour : des en-têtes de sécurité et une Content-Security-Policy définis dans next.config.js, un manifeste web et des icônes qui permettent de l’installer sur un écran d’accueil, avec une route /offline derrière, un bloc JSON-LD Person pour la recherche, et une seule police auto-hébergée via next/font — le premier rendu n’attend ainsi aucun hôte tiers.
Ce qui se passe quand vous appuyez sur Envoyer
La seule route serveur du site, et l’ordre dans lequel elle procède — le message est à l’abri dans la base avant que quoi que ce soit ait le droit d’échouer. Cliquer sur le schéma pour l’ouvrir en taille réelle.
Architecture
Ce qui tourne où — une coquille, dix routes, le contenu gardé en dehors d’elles, et l’unique route d’API avec une base de données et un téléphone derrière. Cliquer sur le schéma pour l’ouvrir en taille réelle.