Aller au contenu principal
~/GiwiSoft
Srtbaasrtbaasrtb

Blog statique avec Hexo : mes astuces SEO, performance et dark mode

Giwi 4 min de lecture Web, DevOps

J’utilise Hexo pour ce blog depuis plusieurs mois. C’est un générateur de site statique rapide et flexible, mais j’ai dû l’optimiser au fil du temps pour obtenir de bonnes performances et un référencement correct. Voici mes astuces.

Pourquoi Hexo ?

Avant de plonger dans les optimisations, un mot sur le choix d’Hexo plutôt que Hugo, Jekyll ou 11ty :

  • Node.js : mon écosystème de prédilection, facile à étendre avec des plugins npm
  • Thèmes EJS : des templates JavaScript, puissants et familiers
  • Écosystème : une bonne collection de plugins pour le SEO, les sitemaps, les flux RSS
  • Déploiement : rsync, GitLab Pages, GitHub Pages, tout est possible

SEO : les plugins indispensables

Génération de sitemaps

# _config.yml
plugins:
- hexo-generator-seo-friendly-sitemap

sitemap:
path: sitemap.xml
posts:
priority: 0.8
pages:
priority: 0.5
categories:
priority: 0.4
tags:
priority: 0.3

J’utilise hexo-generator-seo-friendly-sitemap qui génère un sitemap organisé par type de contenu (posts, pages, tags, catégories) avec des priorités bien définies. Google aime ça.

Balises meta et Schema.org

Dans mon layout principal, j’injecte des meta tags structurés :

<!-- head.ejs -->
<meta name="description" content="<%= page.description || config.description %>">
<meta name="keywords" content="<%= page.tags ? page.tags.map(t => t.name).join(', ') : '' %>">

<!-- Open Graph -->
<meta property="og:title" content="<%= page.title %>">
<meta property="og:type" content="article">
<meta property="og:url" content="<%= config.url %><%= page.path %>">

<!-- JSON-LD Schema.org -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "<%= page.title %>",
"author": { "@type": "Person", "name": "Xavier MARIN" },
"datePublished": "<%= page.date.toISOString() %>"
}
</script>

URLs propres

permalink: :title/

Pas de .html dans les URLs, pas de dates dans le chemin. Des URLs courtes et parlantes, meilleures pour le référencement.

Performance : minification et optimisations

Minification automatique

Mes deux alliés : hexo-all-minifier et hexo-filter-optimize.

filter_optimize:
css:
minify: true
exclude: ["*.min.css"]
html:
minify: true
js:
minify: true

all_minifier:
html:
enable: true
css:
enable: true
js:
enable: true
img:
enable: true

Résultat : mes pages HTML passent de ~50 KB à ~12 KB, et les images sont compressées automatiquement. Le score Lighthouse a gagné 15 points.

Images optimisées

Je stocke mes images dans source/images/ et les plugins les optimisent à la génération. Quelques règles :

  • Couverture : une image PNG par article, dimension ~1200x630px (ratio Open Graph)
  • Format : je convertis en WebP quand c’est pertinent
  • Lazy loading : pas de plugin, je laisse le navigateur gérer avec l’attribut natif
<img src="<%= page.cover %>" alt="<%= page.title %>" loading="lazy">

Cache et CDN

J’ai configuré mon reverse proxy (Nginx) pour servir les assets statiques avec un cache agressif :

location /css/ {
expires 365d;
add_header Cache-Control "public, immutable";
}

location /js/ {
expires 365d;
add_header Cache-Control "public, immutable";
}

location /images/ {
expires 30d;
add_header Cache-Control "public, immutable";
}

Dark mode

Mon blog supporte un thème sombre avec un bouton de bascule. Voici comment c’est implémenté :

Le bouton dans le template

<button id="theme-toggle" class="...">
<i id="theme-icon" class="bi bi-sun-fill"></i>
</button>

Le JavaScript

const themeToggle = document.getElementById("theme-toggle");
const themeIcon = document.getElementById("theme-icon");
const html = document.documentElement;

function setTheme(mode) {
if (mode === "light") {
html.classList.remove("dark");
html.classList.add("light");
themeIcon.className = "bi bi-moon-fill";
localStorage.setItem("theme", "light");
} else {
html.classList.remove("light");
html.classList.add("dark");
themeIcon.className = "bi bi-sun-fill";
localStorage.setItem("theme", "dark");
}
}

// Appliquer au chargement
const saved = localStorage.getItem("theme");
if (saved) {
setTheme(saved);
} else if (window.matchMedia("(prefers-color-scheme: light)").matches) {
setTheme("light");
}

Les styles CSS

Le dark mode est entièrement géré via TailwindCSS :

/* Les classes Tailwind sont appliquées en mode dark */
.dark .text-gray-200 { color: #e5e7eb; }
.dark .bg-neutral-950 { background: #0a0a0a; }

/* Light theme overrides */
.light .text-gray-200 { color: #374151; }
.light .bg-neutral-950 { background: #ffffff; }

Important : évitez le flash de contenu non stylé. Le thème doit être appliqué avant le rendu, idéalement dans une balise <script> en tête de page.

Déploiement automatisé

Mon pipeline Giwi-CD exécute la séquence suivante :

  1. npm install — installation des dépendances
  2. npm run clean — nettoyage du cache
  3. npm run style — compilation TailwindCSS
  4. npm run build — génération du site statique
  5. hexo deploy — synchronisation rsync vers le serveur
"scripts": {
"build": "hexo generate",
"style": "npx tailwindcss -i source/css/main.css -o source/css/style.css --minify",
"clean": "hexo clean",
"deploy": "npm install && npm run style && npm run build && hexo deploy",
"dev": "npm run style && hexo server"
}

Conclusion

Hexo est un excellent générateur de blog statique, à condition de l’équiper des bons plugins SEO et performance. Avec quelques optimisations bien placées (sitemaps structurés, minification automatique, cache CDN, dark mode) vous obtenez un blog rapide, bien référencé et agréable à lire.

Le tout tient dans un dépôt Git avec un pipeline CI/CD. Un workflow simple et efficace.

Et vous, quels plugins Hexo recommanderiez-vous ?