shopifywoocommercewordpressdockerseoiaside-projectecommerce

Transition de Shopify à WooCommerce en 12 h avec Claude

Migration Shopify vers WooCommerce de Free My Beer en 12 h avec Claude Code : méthode, scripts, SEO conservé, pièges rencontrés et chiffres.

Free My Beer tournait sur Shopify depuis fin 2025. Ça marchait, je n'avais rien à redire sur le tunnel de commande. Mais tous les mois je payais l'abonnement, les commissions sur les paiements et une poignée d'apps (Instafeed et compagnie) pour une boutique de niche qui vend des bières sans gluten. Et à côté de ça, j'ai un VPS qui fait déjà tourner des WordPress (Oh le Kayou, AFE) et qui s'ennuie.

Je me suis dit qu'une migration Shopify vers WooCommerce, c'était le genre de chantier qu'on repousse pendant des mois parce que ça prend « au moins deux semaines ». Au final, avec Claude Code en pair-programming dans le terminal, la boutique a basculé en 12 h de travail effectif, étalées sur deux jours, entre autres choses. Design identique, SEO conservé, numéros de commande qui continuent.

Page d'accueil de free-my-beer.com après la migration vers WooCommerce

Pour le contexte sur la boutique elle-même, j'en parle dans cet article. Ici, c'est le retour d'expérience technique.

Ce qu'il fallait migrer, et les règles du jeu

L'inventaire côté Shopify : 17 bières, 3 collections, 3 pages, 15 articles de blog, 68 clients, 46 commandes numérotées de #FMB1001 à #FMB1046. Un thème custom en Liquid, une app checkout maison qui rend le téléphone obligatoire, et une facturation automatique Shopify vers n8n, qui génère un PDF, le range dans Google Drive et crée une entrée Notion.

Je me suis fixé des contraintes non négociables avant de toucher une ligne :

La dernière, c'est typiquement le détail qu'on oublie et qui fait paniquer ton comptable six mois plus tard.

La méthode : spec, plan, exécution, revue

J'ai utilisé Claude Code avec Claude Fable 5.1 et le process superpowers. Le principe : on ne code pas tout de suite. Brainstorming, puis spec écrite, puis plan découpé en tâches, puis exécution, puis revue par un sous-agent indépendant qui n'a pas le contexte de celui qui a écrit le code.

Ça paraît lourd pour un side project. En pratique, c'est ce qui a rendu les 12 h possibles. Le premier commit de la branche duplicate-to-wc (le 28/09 à 9 h 14) contient la spec et le plan du thème, pas de code. Ensuite, Claude exécute le plan tâche par tâche, un sous-agent relit, et je tranche quand il y a une décision à prendre. Très peu de retours en arrière, parce que les questions structurantes ont été posées avant.

La répartition des rôles a été assez nette. Claude a écrit le code, les scripts, les tests, et a fait les vérifications. Moi j'ai choisi les briques (Stripe, Boxtal), validé les grilles tarifaires, géré le DNS, les secrets et toutes les connexions OAuth. Rien de ce qui touche à un compte ou à de l'argent n'est passé sans moi.

Jour 1, matin : la stack locale et le thème

Tout tourne en Docker : MariaDB, WordPress en php8.3-apache, un conteneur wp-cli dédié et un conteneur Node pour compiler Tailwind.

La pièce centrale, c'est un setup.sh idempotent en WP-CLI. Il installe WooCommerce, crée les pages françaises, configure la zone de livraison France, la TVA, les plugins. On peut le relancer dix fois, il ne casse rien et ne duplique rien. Ça change tout quand on alterne entre local et prod.

Le thème fmb-woo reproduit le design Shopify. J'ai gardé la logique des templates Shopify : le contenu éditorial (textes de la home, blocs, etc.) vit dans des fichiers JSON, pas en dur dans les templates PHP. Toute la logique métier est dans un mu-plugin fmb-core : métachamps produit, réglages, redirections, livraison, commandes, emails, factures.

Pour caler le style, on a fonctionné en aller-retour de captures d'écran. Je montrais le rendu Shopify et le rendu WooCommerce côte à côte, je pointais les écarts (fond blanc au lieu de crème, boutons, tableaux, espacements), Claude corrigeait en Tailwind et redéployait. Pas glamour, mais c'est comme ça qu'on arrive à « à l'identique » et pas à « ressemblant ».

Jour 1, midi : migrer les données sans doublons

La migration se fait en deux temps. Un script Node interroge l'API Admin GraphQL de Shopify (version 2025-07, auth en client credentials) et écrit tout en JSON. Ensuite un import PHP lit ces JSON et crée les produits, collections, pages, articles de blog, menus, clients et commandes dans WooCommerce.

Chaque objet importé garde une clé _fmb_shopify_id. Si l'objet existe déjà, on le met à jour au lieu d'en créer un nouveau. Résultat : l'import est rejouable à volonté, et on peut cibler un type d'objet :

# Réimporter uniquement les produits, sans toucher au reste
docker compose run --rm wpcli /scripts/import.sh --only=products

# Tester sur des fixtures sans rien écrire
docker compose run --rm wpcli /scripts/import.sh --only=products,orders --dry-run --data=/migration/fixtures/data

L'API n'expose pas tout l'historique de commandes de façon exploitable, donc j'ai complété avec l'export CSV de l'admin Shopify : 46 commandes via l'API, 186 lignes d'historique via le CSV.

Jour 1, après-midi : prod, paiement, livraison, bascule

Déploiement sur le VPS derrière Traefik avec Let's Encrypt, comme mes autres sites. Trois scripts : deploy.sh, migrate-to-prod.sh, et des sauvegardes en cron.

Pour le paiement, Stripe : checkout classique plus les boutons de paiement express. La livraison, c'est trois options : Colissimo à domicile par tranches de poids, point relais Mondial Relay via Boxtal Connect, et retrait local. Les grilles vivent dans wordpress/wp-content/mu-plugins/fmb-core/shipping.json, que je peux modifier sans toucher au code (extrait) :

{
  "colissimo": {
    "title": "Colissimo domicile (2 à 4 jours ouvrables)",
    "default_item_kg": 0.45,
    "tranches": [
      { "max_kg": 2, "prix": 10.9 },
      { "max_kg": 5, "prix": 15.9 },
      { "max_kg": 8, "prix": 19.9 },
      { "max_kg": 30, "prix": 24.9 }
    ]
  }
}

Emails transactionnels via le SMTP OVH. Puis les redirections 301 de toutes les URL Shopify vers leurs équivalents WooCommerce : /products/…, /collections/…, /pages/…, /blogs/…, /cart, /account, /policies/…. Chaque URL indexée par Google doit atterrir quelque part de cohérent, sinon on perd le peu de SEO qu'une boutique de niche a mis des mois à gagner.

Bascule DNS le 28/09 en fin de journée.

Jour 2 : SEO, factures et perf

Côté SEO, Rank Math avec les métas copiées depuis Shopify, le sitemap, et noindex sur le panier et le compte. J'ai aussi ajouté un llms.txt pour les agents IA qui viennent lire le site.

TVA : 20 % sur les bières avec alcool, 5,5 % sur les sans alcool.

Le morceau le plus satisfaisant, c'est la facturation. Le workflow n8n existait déjà pour Shopify. J'ai collé son JSON dans le chat. Claude l'a lu, l'a adapté au payload WooCommerce, l'a testé en local contre un faux n8n qui exécutait le vrai code du workflow, puis l'a créé directement dans mon n8n via le serveur MCP de n8n. Le flux final : WooCommerce envoie un webhook signé en HMAC, n8n génère le PDF avec Gotenberg, le range dans Drive, crée l'entrée Notion, et renvoie l'URL de la facture à WordPress. Le client a un bouton « Facture » dans Mon compte et la reçoit par email.

Dernier chantier, la perf mobile. Lighthouse est passé de 63 à 98, et le LCP de 8,2 s à 2,3 s, sans plugin de cache. Le gros du gain venait d'un seul fichier : l'image hero était un PNG de 774 Ko. Convertie en WebP (63 Ko), avec srcset et preload, ça règle déjà l'essentiel. Le reste : polices Google auto-hébergées, Cache-Control à un an plus gzip sur les assets, et suppression des scripts inutiles. Le flux Instagram passe par un plugin gratuit.

Dernier commit de finition le 29/09 à 11 h 28.

Trois problèmes et comment ils ont été réglés

Les frais de port majorés de 20 %. WooCommerce considère que le coût d'une méthode de livraison est HT, et il ajoute la TVA par-dessus. Ma grille était en TTC (les prix que je veux afficher). Résultat : 10,90 € devenaient 13,08 € au checkout. Je l'ai vu en testant le panier en prod, et c'est corrigé dans la foulée en convertissant la grille en HT au moment de déclarer le tarif :

// wordpress/wp-content/mu-plugins/fmb-core/shipping.php
// Les grilles sont en TTC ; WooCommerce attend un coût HT et ajoute la TVA livraison.
function fmb_shipping_cost_ht( float $ttc ): float {
	if ( $ttc <= 0 || ! wc_tax_enabled() ) {
		return $ttc;
	}
	$rate = 0.0;
	foreach ( WC_Tax::get_shipping_tax_rates() as $r ) {
		$rate += (float) $r['rate'];
	}
	return $rate > 0 ? round( $ttc / ( 1 + $rate / 100 ), 4 ) : $ttc;
}

Rank Math muet. Plugin installé, métas remplies, et rien dans le <head> côté public. Rank Math n'émet rien tant que l'étape d'inscription à leur compte n'est pas « sautée ». Il faut passer l'option rank_math_registration_skip à true, ce que setup.sh fait maintenant.

La FAQ des articles de blog, perdue. Celui-là n'est pas réglé. Les jetons de l'app Shopify expiraient, et l'app a été coupée avant la fin de l'export. La FAQ des articles, stockée dans un métachamp, n'a jamais été récupérée. Le domaine myshopify redirige maintenant vers le nouveau site, et il n'y a pas d'archive sur la Wayback Machine. C'est perdu.

Quelques galères plus petites en bonus. Un chown -R malencontreux sur les dossiers montés depuis git en prod a cassé le git pull (réparé). Le plugin Stripe ne s'installait pas depuis l'admin WordPress à cause des permissions, donc tout est passé en WP-CLI, ce qui est de toute façon plus reproductible.

Et un point que je trouve plutôt positif : certaines commandes, comme l'injection d'un secret dans n8n, ont été refusées par le classifieur de sécurité de Claude Code. Du coup je les ai lancées moi-même. C'est exactement ce que je veux d'un outil qui a un accès shell à ma prod.

Les chiffres

IndicateurValeur
Temps effectif~12 h sur deux jours
Commits69
Fichiers touchés127
Lignes ajoutées~11 700
Données migrées17 produits, 3 collections, 15 articles, 68 clients, 46 commandes API + 186 lignes d'historique CSV
Lighthouse mobile63 → 98
LCP8,2 s → 2,3 s

Côté coûts, le VPS existait déjà et il est partagé avec d'autres sites. Les plugins sont 100 % gratuits : WooCommerce, Stripe, Boxtal Connect, Rank Math, Instagram Feed, Contact Form 7.

Concrètement, j'économise environ 33 € par mois, plus les commissions de transaction Shopify. Et surtout, je sors d'un écosystème où pas mal d'interfaces sont propriétaires et verrouillées : sur Shopify, rendre le téléphone obligatoire au checkout m'avait demandé une app maison. Sur WooCommerce, c'est mon code, sur mon serveur, et je le modifie comme je veux.

La vérification a été systématique à chaque étape : un script smoke.sh de 18 checks, des captures headless Chrome pour comparer les rendus, et Lighthouse en local avant de pousser.

Ce que je referais autrement

J'exporterais tout, y compris les métachamps, avant de toucher quoi que ce soit, et je garderais ces exports hors de portée de la migration. La FAQ perdue vient uniquement du fait que l'export et la coupure de l'app se sont chevauchés. Un dump complet le jour 0, stocké à part, et le problème n'existait pas.

Je testerais aussi un vrai panier en prod beaucoup plus tôt, avec les vraies taxes activées. Le bug HT/TTC n'apparaissait pas avec les données de test.

Ce qui reste à faire

Soumettre le nouveau sitemap dans la Search Console et surveiller les erreurs d'exploration sur les anciennes URL. Surveiller la première vraie commande de bout en bout : paiement, étiquette, facture, email. Et réécrire la FAQ des articles de blog à la main.

Ce qui m'a le plus marqué, c'est où est passé mon temps. Presque pas dans le code. Dans les décisions : quel prestataire de paiement, quelles grilles, quelles URL rediriger vers quoi, quoi accepter comme perte. Claude a produit les 11 700 lignes, mais il n'a pris aucune de ces décisions. Je me demande si c'est ça, le métier de dev sur ce type de projet maintenant : moins écrire, plus arbitrer et vérifier.

← Tous les articles