The Interstice Blog
Astro WP

Ce blog existe en double. Le même contenu, deux stacks, deux expériences de lecture presque identiques. C'est voulu.

Pourquoi Astro

J'avais besoin d'un blog rapide, sans runtime JS inutile. Astro coche toutes les cases : rendu HTML à la compilation, zéro framework imposé, îles interactives au besoin. La version 6 est stable et le DX est agréable, les composants .astro ressemblent à du HTML qu'on aurait rendu raisonnable.

Markdoc plutôt que MDX pour les articles. MDX mélange JSX dans du Markdown, ce qui transforme chaque article en fichier de code. Markdoc garde une syntaxe déclarative ({% callout %}...{% /callout %}) et les composants restent côté Astro, pas dans le contenu. Plus propre à long terme.

Keystatic en production : le problème résolu

Keystatic monte un éditeur visuel sur /keystatic/. Il écrit directement dans les fichiers .mdoc : pas de base de données, pas de CMS hébergé, pas d'API tierce. La source de vérité c'est git, pas une table wp_posts.

Sauf que Keystatic est présenté comme un outil de développement : il nécessite les sources du projet pour fonctionner, et injecte des routes SSR incompatibles avec un build purement statique. En mode output: 'static' sans adapter, astro build échoue avec NoAdapterInstalled.

La solution : mode hybride. Astro 6 permet de mélanger pages statiques prérendues et routes SSR dans le même projet. Les articles du blog sont compilés en HTML pur au build, sans JavaScript serveur à l'exécution. Les routes admin (/keystatic/*, /admin/ et les endpoints d'API internes) sont SSR et servies par un adapter Node.

L'autre contrainte : pour que Keystatic puisse écrire les .mdoc sur le disque du serveur, les sources doivent être présentes sur le VPS, pas seulement le dist/. Le CI/CD rsync donc les sources, puis lance bun run build sur place.

L'architecture complète

┌────────────────────────────────────────────────────────────────┐
│                     VPS Hetzner (Helsinki)                      │
│                                                                 │
│  src/           ← sources Astro + articles .mdoc                │
│  │   └── content/blog/*.mdoc  ← écrits par Keystatic            │
│  dist/          ← généré par bun run build                      │
│       ├── client/   ← pages blog statiques (HTML pur)           │
│       └── server/   ← routes SSR (entry.mjs)                    │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  systemd astro-blog    (Restart=always)                  │  │
│  │  process Node → port local                               │  │
│  └──────────────────────────┬───────────────────────────────┘  │
│                             │                                   │
│  ┌──────────────────────────▼───────────────────────────────┐  │
│  │  Caddy                                                   │  │
│  │  astro-blog.interstice.work → HTTPS + TLS auto           │  │
│  │                                                          │  │
│  │  /keystatic*  basic_auth ─┐                              │  │
│  │  /admin*      basic_auth ─┼──→ reverse_proxy             │  │
│  │  endpoints API basic_auth ─┘                             │  │
│  └──────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────┘

Flux éditorial :
  1. Ouvrir /keystatic/ → éditeur visuel → sauvegarder
     └─ Keystatic écrit le .mdoc dans src/content/blog/
  2. Aller sur /admin/ → bouton Rebuild
     └─ endpoint authentifié → bun run build (~10s) → redémarrage
  3. systemd détecte la sortie → redémarre le process
     └─ le nouveau dist/ sert les pages régénérées

Le mécanisme de rebuild

Keystatic sauvegarde le contenu dans les sources. Mais les pages du blog sont statiques : elles ne sont générées qu'au build. Un article sauvegardé n'est pas visible tant que le site n'a pas été reconstruit.

La solution : un bouton Rebuild sur le tableau de bord /admin/. Il envoie une requête à un endpoint d'API interne authentifié, qui lance bun run build en arrière-plan puis appelle process.exit(0). systemd, configuré en Restart=always, redémarre automatiquement le process avec le nouveau dist/. Le build prend une dizaine de secondes, le redémarrage moins d'une seconde.

C'est intentionnellement brutal : pas de logique de restart interne, pas de PM2. systemd fait le travail.

Mode hybride : comment ça fonctionne

Dans astro.config.mjs, output: 'static' reste la valeur par défaut. C'est au niveau de chaque route SSR qu'on opt-out du prérendu :

// src/pages/admin/index.astro
export const prerender = false;  // ← SSR, pas de build statique

Les pages blog (src/pages/[category]/[...slug].astro, src/pages/index.astro) ne portent pas cette directive : elles sont compilées en HTML pur au build, sans JavaScript serveur. Résultat : les pages blog sont du HTML pur, les routes admin restent dynamiques.

Pourquoi pas le mode GitHub de Keystatic

Keystatic propose un mode alternatif où les sauvegardes transitent par l'API GitHub (commits directs dans le dépôt). Cela supprimerait le besoin de sources sur le VPS et du mécanisme de rebuild.

Problème : GitHub est hébergé sur infrastructure Microsoft/Azure, hors Europe. Toute l'infrastructure The Interstice est sur VPS Hetzner (Helsinki). Faire passer les articles par GitHub pour les réécrire sur le VPS serait incohérent avec cette contrainte. Le mode local, malgré sa complexité, reste 100 % EU.

Le système miroir avec le blog WordPress

Le même contenu est publié en parallèle sur wp-blog.interstice.work (WordPress) et ici (Astro). C'est une comparaison active : temps de chargement, DX d'écriture, référencement, maintenabilité.

Les deux blogs partagent les mêmes tokens CSS (palette, typographie), le même switch WP / Astro dans le header, et la même newsletter (Brevo, via un proxy PHP côté WP). La différence visible : WP affiche des extraits automatiques sous chaque titre, Astro ne le fait pas.

La palette terre et le cube

The Interstice est organisé comme un cube isométrique : trois faces, trois sites.

La palette terre (#7A5C3E#281D12) est réservée aux deux blogs. Le fond dégradé, les cartes parallélogrammes miroirs, le V inversé sous le header : tout ça vient d'un seul système géométrique défini dans tokens.css et propagé sur les cinq sites via un script Python.

Stack complète

  • Astro 6 : mode hybride (static + SSR)
  • Markdoc : format des articles (.mdoc)
  • Keystatic : backoffice en production sur /keystatic/
  • @astrojs/node : adapter standalone, sert le SSR
  • Fraunces : serif display (Google Fonts)
  • JetBrains Mono : monospace UI et code
  • Brevo : newsletter (proxy PHP via le blog WP)
  • VPS Hetzner CX23 (Helsinki) : hébergement EU
  • Caddy : reverse proxy, TLS automatique, Basic Auth
  • systemd : supervision du process Node, restart automatique

Le code source n'est pas public pour l'instant. Si ça change, ce sera sur github.com/OnyxynO.