Aller au contenu principal
GiwiSoft
01

Migration d'un blog WordPress vers Hexo : retour d'expérience

Giwi 6 min de lecture Web, DevOps

J’ai migré mon blog de WordPress vers Hexo il y a quelques mois. Voici pourquoi, comment, et ce que ça a changé.

Cet article n’est pas un procès contre WordPress, qui reste un excellent CMS pour bien des usages. Il s’agit plutôt d’un retour d’expérience: pour un blog technique, essentiellement composé de texte et de code, les avantages d’un générateur statique se sont avérés décisifs. Si vous hésitez à franchir le pas, voici le détail de ce qui m’a décidé et de la façon dont j’ai procédé.

Pourquoi quitter WordPress ?

Critère WordPress Hexo
Temps de chargement ~2.5 s ~0.4 s
Sécurité Mises à jour + plugins + firewall Fichiers statiques
Hébergement Serveur PHP + MySQL Nginx ou S3
Coût mensuel ~15 € (VPS) ~3 € (ou gratuit)
Sauvegardes Base + fichiers Un dépôt Git

Le tableau résume l’essentiel. Un blog statique n’a rien à exécuter: pas de PHP, pas de base de données, pas de processus applicatif. Résultat, il se charge plus vite, il est plus simple à sécuriser (un fichier statique ne se fait pas pirater), et il coûte moins cher à héberger puisqu’un simple serveur de fichiers ou un bucket S3 suffit.

La goutte d’eau qui a mis le feu aux poudres : une nuit de 2025, une mise à jour automatique de plugin a cassé le site. Temps de résolution : 2h. Pour un blog technique, c’est inacceptable.

Ce n’était pourtant pas la première fois qu’un plugin me posait problème. Mais cette fois-là, le site est tombé en pleine nuit, sans intervention de ma part: la mise à jour automatique d’un plugin de sécurité avait provoqué un conflit qui a fait planter le serveur entier. Deux heures pour remonter, avec l’angoisse de possibles pertes de données. C’est à ce moment que j’ai décidé qu’un générateur statique ne m’exposerait jamais à ce genre de veillée.

La migration pas à pas

1. Exporter WordPress

J’ai utilisé le plugin officiel WordPress Exporter pour générer un fichier XML complet.

L’export Wordpress eXtended RSS (WXR) contient l’ensemble des articles, pages, commentaires, catégories et tags dans un seul fichier XML. C’est le format standard, reconnu par la plupart des outils d’import. Le générer prend quelques secondes et ne nécessite aucun accès au serveur : tout est fait depuis l’interface admin.

2. Convertir avec hexo-migrator-wordpress

npm install hexo-migrator-wordpress
hexo migrate wordpress /chemin/vers/export.xml

Les articles sont importés avec leur titre, date, contenu et tags. Mais le formatage Markdown est approximatif: beaucoup de balises HTML subsistent.

C’est l’étape la plus automatique mais aussi la plus imparfaite. WordPress stocke le contenu en HTML (avec le fameux wpautop qui ajoute des <p> et des <br> automatiquement), et le migrator fait de son mieux pour le convertir en Markdown. Le résultat est fonctionnel, mais truffé de restes de balises et d’espaces insécables (&nbsp;). C’est pourquoi l’étape suivante, le nettoyage, est indispensable.

3. Nettoyage automatisé

// scripts/clean-wordpress-import.js
hexo.extend.filter.register('after_post_render', function(data) {
data.content = data.content
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
.replace(/<em>(.*?)<\/em>/g, '*$1*')
.replace(/<br\s*\/?>/g, '\n')
.replace(/&nbsp;/g, ' ');
return data;
});

Plutôt que de nettoyer des dizaines d’articles à la main, un filtre Hexo passe sur le contenu de chaque article lors du rendu. Chaque regex transforme un reste de HTML en syntaxe Markdown équivalente. C’est le genre de micro-script qui transforme une migration fastidieuse en opération quasi automatique, en une vingtaine de lignes.

Ce filtre s’attache au moment after_post_render : il s’exécute après que Hexo a rendu l’article, donc il peut réécrire le contenu avant qu’il ne soit écrit dans le fichier statique final.

4. Migration des images

# Déplacer les uploads WordPress vers le répertoire source Hexo
rsync -av old-blog/wp-content/uploads/ source/images/

Puis remplacer les URLs :

// scripts/fix-image-paths.js
hexo.extend.filter.register('after_post_render', function(data) {
data.content = data.content.replace(
/https?:\/\/old-blog\.com\/wp-content\/uploads\/(\d+\/\d+\/.*?\.(png|jpg|jpeg|gif|svg))/g,
'/images/$1'
);
return data;
});

Les images sont copiées, puis toutes les URLs absolues qui pointaient vers l’ancien site sont réécrites en chemins relatifs vers le nouveau répertoire source/images/. Là encore, une regex dans un filtre évite de toucher chaque article individuellement, et garantit que toutes les images, sans exception, pointent au bon endroit.

5. Migration des URLs

WordPress utilise /YYYY/MM/DD/slug/. Hexo par défaut aussi (:year/:month/:day/:title/). J’ai gardé la même structure pour ne pas casser les liens entrants :

# _config.yml
permalink: ':year/:month/:day/:title/'

Conserver la structure d’URL était une décision importante: toutes les redirections et les liens entrants (Google, autres sites) pointent vers les anciennes URLs. En gardant exactement le même schéma, aucun redirect n’est nécessaire et l’historique SEO est préservé sans effort supplémentaire.

Ce que j’ai perdu

  • Commentaires: remplacés par un lien GitHub Discussions (moins de spam)
  • Éditeur visuel: j’écris en Markdown dans VS Code (plus rapide)
  • Plugins analytics: Umami auto-hébergé, plus léger et respectueux

La perte la plus significative en apparence est celle des commentaires. Mais je l’ai transformée en avantage: la modération et l’anti-spam inhérents aux commentaires WordPress demandaient du temps, alors qu’un lien vers GitHub Discussions me laisse le choix des conversations et élimine quasi totalement le spam automatisé.

Ce que j’ai gagné

graph LR A[VS Code] --> B[Git push] B --> C[GitHub] C --> D[CI/CD] D --> E[Nginx] E --> F[HTML statique]

Un git push et le blog est déployé. Plus de PHP, plus de MySQL, plus d’attaques par injection.

Le flux de travail s’est radicalement simplifié : j’écris un article en Markdown dans VS Code, je fais un git push, et une pipeline CI/CD construit le site statique et le déploie sur Nginx. L’absence de PHP et de MySQL supprime toute une classe d’attaques (injection SQL, exécution de code côté serveur), et le workflow Git me donne un historique complet de chaque changement.

Performances avant/après

Métrique WordPress Hexo
Lighthouse Performance 72 98
First Contentful Paint 1.8 s 0.3 s
Requêtes HTTP 45 8
Taille page 1.2 Mo 180 Ko

Les chiffres parlent d’eux-mêmes. La réduction drastique des requêtes HTTP (45 -> 8) et de la taille de page (1.2 Mo -> 180 Ko) vient surtout de l’absence de plugins qui injectaient des scripts et des styles inutiles sur chaque page. Un blog statique n’emporte que ce dont il a réellement besoin.

Mon conseil

Si votre blog est principalement du texte et du code, le statique est supérieur sur tous les plans. WordPress n’a d’intérêt que si vous avez besoin d’un CMS dynamique avec des utilisateurs non techniques.

C’est le critère décisif: avez-vous besoin d’utilisateurs non techniques qui éditeraient le contenu via une interface? Si la réponse est non (ce qui est le cas d’un blog technique dont l’auteur écrit du code), alors la complexité de WordPress n’apporte aucun bénéfice. Le Markdown et Git suffisent largement, et enlèvent toute la couche de maintenance.

La migration m’a pris un weekend. Depuis, je n’y pense plus et c’est le meilleur compliment qu’on puisse faire à un outil.

Si vous êtes convaincu, notez qu’un weekend suffit: un pour exporter et configurer, quelques heures pour nettoyer l’import. Ensuite, chaque article se rédige plus vite qu’avant, et le déploiement est un simple git push. Le coût permanent, lui, tombe à presque rien.