Architecte & développeur fullstack freelance
@ Ctrl+Shift
En freelance pour Ctrl+Shift, je prends en charge des applications TypeScript de l'architecture à la mise en production, de la boutique Shopify headless à l'outil sur mesure. Genaveh, chocolatier luxembourgeois, a fait appel à moi trois fois en moins d'un an.
- missions livrées
- 4
- missions confiées par le même client
- 3
- applications en production
- 2
-
oct. 2026 1 semaine
Référencement Google & moteurs IA
Genaveh · client récurrent
↓ Voir les détailsSept mois après la refonte de leur boutique, Genaveh revient pour être mieux compris des moteurs de recherche et des assistants IA. Six types de données structurées et un /llms.txt, générés depuis Shopify.
- types de données structurées schema.org
- 6
- tests unitaires
- 33
- nouvelle dépendance npm
- 0
TypeScriptReact 19TanStack StartShopify Storefront APIGraphQLschema.orgJSON-LDRsbuildNode.jsLe problème
Pour un moteur de recherche ou un assistant IA, une page produit n’est que du texte : rien ne dit explicitement qu’il s’agit d’un produit, à quel prix, en stock ou non, vendu par quelle marque et dans quelles boutiques. Sur le site de Genaveh, aucune donnée structurée n’était publiée et
/llms.txtrenvoyait une 404.Ce que j’ai livré
- 6 types schema.org :
OrganizationetWebSitesur tout le site,Storepour les deux boutiques physiques,Productsur les fiches produit,BreadcrumbListsur les pages à fil d’Ariane,FAQPagesur la page contact - Un
/llms.txtdynamique : description de la boutique, collections, boutiques et pages clés, régénéré depuis Shopify et mis en cache une heure - Un gain de performance au passage : la bibliothèque de cartographie Mapbox se retrouvait dans le bundle principal à cause d’un import ; elle n’est plus chargée que sur la page contact
- Fiable : 33 tests unitaires sur la génération, sans aucune dépendance ajoutée
Sous le capot
- Générateurs purs : chaque type est une fonction qui prend les données Shopify déjà chargées par la page et renvoie du JSON-LD, testable sans serveur ; le rendu passe par le
head()du routeur TanStack, qui l’échappe côté serveur - Une seule source de vérité : le fil d’Ariane visible et son JSON-LD sont construits par la même fonction, mêmes niveaux, mêmes libellés, mêmes URL localisées
- Prix identiques à l’affichage :
Offerpour un produit à variante unique (avec SKU et code-barres GTIN),AggregateOffersinon, disponibilitéPreOrderpour les produits en précommande - Parsing strict : adresses et horaires des boutiques sont lus depuis le store locator ; un format non reconnu est omis, jamais deviné
- URL canonique en constante : derrière le proxy, la production génère des URL en
http://; aucun lien absolu ne dépend de la requête - FAQ : le balisage s’active tout seul dès que des questions sont publiées dans Shopify
Dans la foulée
- Migration Vite → Rsbuild : montée de version de TanStack Start, sortie Nitro remplacée par un serveur
srvx, plugins Paraglide et Sentry passés en variante Rspack - Barre de livraison offerte dans le panier, avec seuil selon le pays de livraison, en trois langues
- Précommandes des calendriers de l’Avent, signalées sur les boutons d’achat et transmises à la commande
Choix assumés
Option écartée Pourquoi Recopier la FAQ dans le code Le balisage est généré depuis la section FAQ du CMS : une seule saisie, aucun risque d’écart avec la page Déduire les horaires d’un texte libre Une donnée fausse dans Google est pire qu’une donnée absente URL absolues tirées de la requête En production, elles sortent en http://derrière le proxyVitest ou Jest Le runner de tests natif de Node suffit : zéro dépendance, rien à maintenir -
janv. 2026 → mars 2026 2 mois
Boutique Shopify headless
Genaveh
↓ Voir les détailsLa boutique en ligne de Genaveh, chocolatier luxembourgeois, reconstruite en front sur mesure au-dessus de Shopify. Shopify garde le catalogue, le paiement et les commandes ; le site, lui, est rendu côté serveur en React, en trois langues, et l'équipe du client compose ses pages sans passer par un développeur.
- sections de contenu pilotées depuis Shopify
- 18
- domaines métier isolés dans un monorepo
- 6
- langues : français, anglais, allemand
- 3
TypeScriptReact 19TanStack StartTanStack QueryShopify Storefront APIGraphQLTailwind CSSParaglide JSRedisNodemailerSentryPlaywrightStorybookTurborepoDockerLe problème
Un thème Shopify classique enferme la boutique dans son moteur de templates : mises en page figées, peu de contrôle sur le rendu, et chaque nouvelle page de contenu demande une intervention technique. Pour une marque qui vend en France, au Luxembourg et en Allemagne, il fallait un site sur mesure sans perdre ce que Shopify fait bien : catalogue, paiement, commandes.
Ce que nous avons construit
Un storefront headless développé en binôme : Shopify sert de back-office, le front est une application React rendue côté serveur.
- Parcours d’achat complet : catalogue, fiches produit à variantes (sélection reflétée dans l’URL), collections filtrables et triables, recherche prédictive, panier en tiroir et en page avec mises à jour optimistes, Shop Pay
- Espace client : inscription, connexion, activation, mot de passe oublié, historique et suivi de commandes, carnet d’adresses
- Contenu en autonomie : 18 sections (hero, bannières, sliders produits, FAQ, avis Trustpilot, carte des boutiques Mapbox…) que l’équipe du client assemble page par page depuis l’admin Shopify, sans redéploiement
- International : français, anglais, allemand ; 245 pays, chacun avec sa devise et ses prix
- Contact sécurisé : formulaire protégé par Cloudflare Turnstile vérifié côté serveur, e-mails HTML dans la langue du visiteur
Sous le capot
Un monorepo découpé par domaine métier plutôt que par type de fichier :
apps/web TanStack Start : routes, rendu serveur, server functions domains/ 6 domaines, chacun avec ses requêtes, composants et traductions account · acquisition · commerce · discovery · payment · platform packages/ socle partagé graphql · storefront · design-system · paraglide · environment · mailUne fonctionnalité vit dans un seul dossier : le panier dans
payment, la recherche dansdiscovery, les comptes clients dansaccount. Chaque domaine n’expose que ses composants d’écran et ses fonctions serveur.- Rendu serveur : pages rendues par TanStack Start, appels à Shopify dans des server functions ; la clé privée de l’API Storefront ne quitte jamais le serveur
- Données typées : 78 requêtes, mutations et fragments GraphQL, types générés depuis le schéma Shopify
- Pages composables : les sections sont des metaobjects Shopify référencés par un metafield ; chaque type est rendu par son composant React
- Cache : couche Redis devant l’API Storefront, désactivée en développement
- Traductions : Paraglide JS, messages compilés en fonctions typées, une clé manquante casse le build
- Exploitation : Sentry, consentement cookies, analytics (Shopify, Meta Pixel, Klaviyo, Rybbit), tests e2e Playwright, image Docker de production
Choix assumés
Option écartée Pourquoi Thème Shopify Liquid Rendu et mise en page bornés par le moteur de thème Pages codées en dur Chaque nouvelle page aurait demandé un développeur ; les sections metaobjects rendent l’équipe autonome Appels Shopify depuis le navigateur Expose la clé d’API et empêche de mettre les réponses en cache côté serveur Découpage par type de fichier Une fonctionnalité éclatée dans tout le dépôt ; par domaine, elle tient dans un dossier -
janv. 2026 1 semaine
Application Shopify de facturation
Genaveh
↓ Voir les détailsUne application Shopify qui génère et envoie automatiquement une facture PDF aux couleurs de la boutique, dans la langue du client, à chaque commande payée. Conçue, développée et mise en production en autonomie, de l'architecture au déploiement.
- intervention manuelle par commande payée
- 0
- langues : français, anglais, allemand
- 3
- mises en page d'email personnalisables
- 6
TypeScriptRemixReactShopifyGraphQLPDFKitNode.jsPrismaNodemailerReact EmailZodTipTapParaglide JSDockerGitLab CILe problème
Shopify n’envoie pas de facture PDF au client final : le marchand doit la produire et l’expédier lui-même, commande par commande. Une tâche répétitive, source d’oublis, et rarement traduite dans la langue de l’acheteur.
Ce que j’ai construit
Une application intégrée à l’interface d’administration Shopify, qui prend tout en charge dès le paiement :
- Zéro intervention — chaque commande payée déclenche la génération de la facture et son envoi par email au client
- Aux couleurs de la boutique — logo, couleurs, police, mentions légales et 6 mises en page d’email au choix
- Dans la langue du client — factures et emails en français, anglais et allemand, langue détectée depuis la commande
- Pilotable depuis Shopify — liste des commandes avec recherche, téléchargement ou renvoi d’une facture, éditeur de texte riche pour les emails
- Fiable — un webhook relivré par Shopify ne renvoie pas une facture déjà envoyée ; une panne SMTP passagère déclenche une nouvelle tentative automatique
- En production — déployée automatiquement par un pipeline CI/CD
Sous le capot
Commande payée └─ Webhook orders/paid → vérification HMAC + idempotence par ID de livraison └─ Admin API GraphQL → commande complète (lignes, taxes, adresses, client) └─ PDFKit → facture générée en flux, dans la langue du client └─ Nodemailer + React Email → envoi SMTP, PDF en pièce jointe- Remix (loaders / actions) — toute la logique métier côté serveur ; les modules
.server.tsne sont jamais embarqués dans le bundle client - Intégration Shopify — OAuth, sessions et vérification HMAC via
@shopify/shopify-app-remix; interface embarquée dans l’Admin avec Polaris et App Bridge - Données — configuration stockée dans les metafields de la boutique ; Prisma + SQLite ne conservent que les sessions et le suivi des webhooks
- Sécurité — mot de passe SMTP chiffré en AES-256-GCM, aucune donnée client ni commande persistée, webhooks de conformité RGPD gérés
- Résilience SMTP — délais bornés (10 s à la connexion, 30 s sur le socket) et réponse
503sur erreur transitoire pour que Shopify relance - Formulaires et contenu — validation Zod + React Hook Form, éditeur riche TipTap, libellés de facture traduits avec Paraglide JS
- Déploiement — image Docker multi-étapes, pipeline GitLab CI vers Dokploy, derrière Cloudflare ; documentation API générée avec TypeDoc
Choix assumés
Choix Pourquoi Metafields Shopify plutôt qu’une base dédiée La configuration suit la boutique : aucune donnée marchand à héberger ni à migrer PDF généré en flux Aucun fichier temporaire sur le serveur : la facture part directement en téléchargement ou en pièce jointe Idempotence par ID de webhook Shopify peut livrer plusieurs fois le même événement : le client ne reçoit qu’une facture -
janv. 2025 → avr. 2025 3 mois
Convertisseur InDesign → Web
Projet interne
↓ Voir les détailsUn moteur qui transforme une maquette Adobe InDesign en page web fidèle au pixel près. Le texte reste du vrai texte — sélectionnable, indexable, accessible — là où les solutions classiques produisent une image ou un PDF figé.
- tests automatisés
- ~200
- types de fichiers InDesign interprétés
- 7
- dépendances de production
- 5
TypeScriptReact 19ViteNode.jsfast-xml-parseradm-zipsharpVitestLe problème
Plaquettes, catalogues, magazines : la mise en page professionnelle se fait sur Adobe InDesign. Pour publier ces documents sur le web, on se contente souvent d’un PDF ou d’une capture — lourds, illisibles sur mobile, invisibles pour Google comme pour les lecteurs d’écran.
Ce que j’ai construit
Un convertisseur qui lit le fichier source InDesign (
.idml) et le restitue en composants React positionnés au pixel près :- Fidélité visuelle — textes, images, dégradés, formes vectorielles, transparences et polices d’origine
- Contenu vivant — le texte se sélectionne, se copie, se recherche et s’indexe
- Adaptatif — la page s’ajuste à la largeur de l’écran sans déformer la typographie
- Fiable — ~200 tests automatisés sur la logique de conversion, soit ~0,7 ligne de test par ligne de code
- Léger — 5 dépendances de production, aucun service externe
Sous le capot
Trois phases strictement séparées, chacune avec ses propres types TypeScript :
.idml (ZIP de XML) └─ Parse → ParsedIdml valeurs InDesign brutes (points, références) └─ Resolve → ResolvedDocument valeurs prêtes pour le web (px, CSS) └─ Render → composants React HTML à positionnement absoluUn bug se localise immédiatement : une valeur en pixels fausse vient forcément du résolveur, jamais du parseur ni des composants.
- Lecture du format — décompression en mémoire et parsing de 7 types de fichiers XML (pages, textes, styles, couleurs, polices…) avec
fast-xml-parser - Géométrie — matrices affines 2D pour passer du repère de la double page à celui de chaque page, groupes imbriqués compris
- Styles — reproduction de la cascade à 4 niveaux d’InDesign et de l’héritage
basedOn; les chaînes d’héritage sont aplaties une fois par document, pas à chaque fragment de texte - Couleurs — conversion CMJN → RVB, teintes et dégradés linéaires (angle InDesign → CSS)
- Images — trois modes de stockage (base64, embarqué, lié) et conversion TIFF → JPEG via
sharp - Mise à l’échelle —
transform: scale()plutôt que des pourcentages : seule primitive CSS qui redimensionne aussi tailles de police, interlignages et approches
Choix assumés
Option écartée Pourquoi Navigateur headless / PDF Produit une image figée, pas un DOM interrogeable Canvas Ni sélection de texte, ni accessibilité Flexbox / Grid InDesign ne connaît que des coordonnées : le positionnement absolu est le seul mapping 1:1 sans perte