WordPress vers un site statique avec Claude Code : comment j'ai reconstruit ce site en un après-midi

Publié
11 juillet 2026
Par
Jacob Lloyd — rédigé avec l'aide de l'IA, une fois le projet terminé
Temps de lecture
9 min de lecture

En clair : L'histoire de la reconstruction de ce site même, en un seul après-midi, par un assistant de codage IA — passé de WordPress à un simple dossier de fichiers. Le nouveau site est plus rapide, gratuit à faire tourner, et n'a plus de page de connexion à pirater. L'article se termine par la recette étape par étape pour faire pareil sur votre propre site.

Ce site tournait autrefois sous WordPress. Aujourd'hui, c'est un dossier de fichiers Markdown et un script Python de 183 lignes, construit à partir d'un export WordPress par un agent de codage IA en un après-midi. Voici comment ça s'est passé, et pourquoi le même workflow fait toujours tourner la boutique.

tl;dr
  • De quoi il s'agit : un générateur de site statique fait maison (Python + Jinja2) qui a remplacé WordPress, plus un importateur à usage unique pour l'ancien contenu
  • Ce que ça coûte : rien. Pas de plugin, pas de licence de thème, le même hébergement mutualisé bon marché qu'avant
  • Ce qu'il faut : votre export WordPress (un fichier WXR), votre dossier uploads, et un agent de codage IA — j'ai utilisé Claude Code
  • Ce qu'on obtient : des fichiers HTML plats, pas de base de données, pas de page de connexion à attaquer par force brute

Ce qu'on obtient

D'abord, les preuves — la vraie sortie du terminal, le jour où la reconstruction a été livrée :

$ python3 _build/build.py
Built 15 files (7 projects, 3 pages, 4 categories).

Quinze pages HTML, c'était tout le site au moment de la migration : la page d'accueil, quatre pages de catégories, sept articles de projets, trois pages d'information. Tout est pré-rendu, tout est en fichiers plats, zéro code côté serveur exécuté à chaque requête.

L'export WordPress d'où tout ça venait pesait 572 Ko pour 123 éléments — articles, pages, pièces jointes images, et un tas de brouillons abandonnés. Sept vrais articles ont survécu au voyage. Le reste, c'était de la friche que l'importateur a sciemment ignorée.

Et la machine qui construit tout ça tient dans un tableau :

ÉlémentLignes
build.py (le générateur)183
migrate.py (l'importateur à usage unique)166
Templates (5 mises en page)139
Partiels (nav, pied de page, carte)51
site.yaml (toute la config du site)36
serve.sh (prévisualisation locale)12
.htaccess (règles serveur)57
Total644

Ajoutez le CSS (322 lignes) et l'unique fichier JavaScript (31 lignes — un bouton de nav mobile, rien de plus) et le site entier fait moins de 1 000 lignes. Pas de node_modules, pas de framework de build, pas de lockfile à négocier.

Partie 1 : quitter WordPress

Je n'ai rien écrit à la main. J'ai décrit ce que je voulais à Claude Code, qui a écrit l'importateur, puis le générateur, puis les templates, pendant que mon rôle se limitait à relire le résultat et à dire « non, plutôt comme ça ». Les horodatages des fichiers le confirment : de l'export en main à un site construit et navigable en moins de deux heures. Un après-midi, pas un week-end.

Le pipeline final ressemble à ça :

L'export et le miroir des médias

WordPress exporte tout via Outils → Exporter → Tout le contenu. Vous obtenez un fichier WXR — du XML avec vos articles, pages, catégories, et enregistrements de pièces jointes, tout entassé dedans. C'est la seule entrée de l'importateur.

La seule chose que l'export n'inclut pas, ce sont vos fichiers image eux-mêmes — seulement leurs URL. Le dossier uploads doit donc être récupéré séparément — SFTP, un gestionnaire de fichiers, ou un simple miroir wget — vers assets/uploads/, en conservant la même structure année/mois que WordPress utilisait, pour que les URL réécrites pointent au bon endroit.

Ce que fait vraiment migrate.py

L'importateur est à usage unique, mais on peut le relancer sans danger. Pas de bibliothèque XML — juste des regex sur le texte WXR, ce qui paraît fragile jusqu'à ce qu'on remarque que le XML d'export WordPress est extrêmement régulier, et que le grep coûte beaucoup moins de code qu'un parseur complet. Pour chaque article, il récupère le titre, le slug, la date, le corps, la catégorie et l'image mise en avant, puis :

  • Ignore la friche — les articles vides et tout ce qui s'intitule « (draft)… » n'atteint jamais content/
  • Fait correspondre les catégories à un petit jeu de clés propres, plutôt qu'aux nicenames en pagaille de WordPress
  • Résout les images mises en avant via l'id de la pièce jointe, avec repli sur la première image uploadée dans le corps
  • Réécrit les URL de médias de wp-content/uploads/… vers /assets/uploads/…
  • Réécrit les liens internes — les anciens permaliens deviennent les nouvelles URL propres, via une table de correspondance des slugs construite à partir de tous les articles et pages
  • Supprime les commentaires de blocs Gutenberg et efface carrément les blocs de listing dynamique — l'étape de build génère maintenant les listes de catégories, donc les anciennes listes figées ne feraient que moisir sur place
  • Génère les extraits automatiquement — retire les balises, tronque à 160 caractères, utilisé comme texte de carte et meta description

Le plus intéressant : les articles ressortent de l'autre côté sous forme de HTML WordPress nettoyé, posé tel quel dans un fichier Markdown, pas converti en syntaxe Markdown. Le moteur de rendu fait passer le HTML brut tel quel, donc rien n'a été perdu de l'ancien contenu — et les nouveaux articles s'écrivent en Markdown ordinaire. Les deux se construisent très bien depuis le même dossier. La première fois que j'ai ouvert un fichier « .md » composé à 90 % de balises HTML, j'ai cru que quelque chose avait cassé. Non.

Ce que fait build.py

Voici la partie qui tourne pour de bon, sans fin. Il lit site.yaml pour la nav, les catégories et les liens, lit tout ce qu'il y a dans content/, et fait passer chaque fichier par un template Jinja2 pour produire un vrai dossier contenant un index.html — des URL propres sans aucune règle de réécriture côté serveur. Au-delà des pages elles-mêmes, il produit :

  • Une barre de nav et un pied de page construits à partir des données — ajoutez une catégorie ou une page, elle apparaît toute seule
  • Titre, meta description, URL canonique, balises Open Graph et Twitter, par page
  • sitemap.xml, robots.txt, et un manifeste web, régénérés à chaque exécution
  • Une passe de nettoyage qui supprime d'abord l'ancienne sortie, pour qu'un article renommé ne laisse pas traîner une page morte
  • Zéro dépendance à l'exécution : polices auto-hébergées, icônes SVG inline, cet unique petit fichier JS, rien qui vienne d'un CDN

Ajouter un article, c'est : déposer un fichier Markdown dans content/projects/, lancer le build. C'est tout le workflow.

Prévisualiser, puis déployer

La prévisualisation locale tient en un script — build, puis serve. On clique sur chaque page avant que quoi que ce soit n'approche du vrai hébergement.

./serve.sh
# builds, then serves the whole site at http://localhost:8099

Le déploiement est un push rsync guidé par un manifeste qui liste exactement ce qui part (HTML, CSS, JS, images, polices, .htaccess) et ce qui est refusé (les scripts Python, les sources Markdown, l'export WordPress, les templates). La config serveur ajoute la compression, les en-têtes de cache, les en-têtes de sécurité, et un refus catégorique de récupérer directement n'importe quel fichier source — donc même si quelqu'un sait que le générateur existe, il n'y a rien à télécharger.

Partie 2 : comment ce site est maintenu aujourd'hui

Voici ce qui rendait la reconstruction utile de la faire proprement : le site n'est plus maintenu à la main non plus. Le même workflow d'agents qui l'a construit le maintient à jour — y compris la mise à jour que vous êtes en train de lire.

Cette révision a commencé par un essaim d'audit. Une équipe a vérifié chaque page publiée et cliqué sur chaque lien sortant ; une autre a recueilli des captures d'écran et des faits techniques précis sur le fonctionnement du site. Tout ça a nourri un seul document de spécification : construire ceci, n'inventer aucun chiffre, ne jamais publier de secret.

À partir de là, le travail s'est réparti — un agent plateforme (structure des catégories, une deuxième langue, la plomberie côté moteurs de recherche), un designer, et un agent rédacteur par article, celui-ci inclus. Un modèle rapide rédige chaque page à partir de son fichier de faits. Un modèle plus costaud relit pour le ton et l'exactitude. Puis une passe adversariale essaie de tout casser — en revérifiant les chiffres par rapport aux faits sources, en recliquant les liens, en traquant tout ce qui sent l'hallucination. Ou, honnêtement, tout ce qui sent l'IA. Ce qui, dans ce cas précis, est vrai.

Confiez le même travail à votre propre agent

Vous avez un vieux site WordPress que vous ne voulez plus rafistoler éternellement ? Voici la recette que j'ai donnée au mien. Décrivez chaque étape à votre agent et laissez-le écrire le code.

  1. Exportez. Administration WordPress → Outils → Exporter → Tout le contenu. Déposez le fichier WXR là où l'agent peut le lire, par exemple _build/source.xml.
  2. Faites un miroir des médias. Copiez wp-content/uploads/ depuis l'ancien hébergement vers assets/uploads/, en conservant la structure année/mois.
  3. Demandez un script de migration. Parser les éléments WXR, ignorer les brouillons et les articles vides, faire correspondre les catégories à une courte liste propre, résoudre les images mises en avant (avec repli sur la première image inline), réécrire les URL d'upload et les liens internes, supprimer les commentaires de blocs Gutenberg, générer les extraits automatiquement, écrire des fichiers Markdown avec frontmatter.
  4. Demandez un générateur. Lire une config de site plus le dossier de contenu, faire passer le tout par des templates (base, accueil, catégorie, article, page, partiels nav/pied de page/carte partagés), produire des dossiers à URL propre, générer sitemap.xml, robots.txt, et un manifeste web.
  5. Reconstruisez le design de zéro. Ne portez pas le CSS de l'ancien thème — une feuille de style propre, des polices ouvertes auto-hébergées, des icônes SVG inline, zéro requête externe.
  6. Prévisualisez tout. Un script d'une ligne qui build puis sert en local. Cliquez sur chaque page.
  7. Écrivez la config serveur. Compression, en-têtes de cache, en-têtes de sécurité, règles de refus pour les fichiers source, un repli pour les URL sans extension.
  8. Déployez avec un manifeste. rsync la sortie, en excluant explicitement les dossiers source et les scripts, puis vérifiez que l'URL en ligne renvoie bien 200.
  9. Gardez l'export. Le fichier WXR reste dans le projet — relancer le script de migration régénère proprement le dossier de contenu si jamais vous avez besoin d'un nouvel import.

La liste complète des dépendances à l'exécution, en entier :

pip install jinja2 pyyaml markdown

C'est tout.

Pièges à éviter

  • Oubliez de faire le miroir du dossier uploads et rien ne vous prévient. Le build réussit pendant que chaque miniature renvoie une 404. Découvert de la manière lente.
  • Sortie et source partagent le même dossier. Le HTML construit atterrit dans la même racine de projet que les sources Python et Markdown — toute la séparation du déploiement repose sur la liste allow/deny. Ça a semblé à l'envers au début ; c'est justement ce qui permet au déploiement de rester un simple rsync.
  • Renommer une catégorie peut laisser un dossier mort derrière soi. L'étape de nettoyage a les noms de catégories écrits en dur. Renommez-en une dans la config, oubliez cette liste, et l'ancien dossier reste publié et orphelin.
  • Les dates se trient comme des chaînes de caractères, pas comme des dates. Ça marche tant que chaque date est écrite YYYY-MM-DD. Une seule faute de frappe et votre article le plus récent coule discrètement tout en bas.

← Plus de IA et LLM locaux