Astro 6.4 en profondeur : pipeline Markdown enfichable, Sätteri propulsé par Rust et révolution du déploiement Cloudflare
Le 28 mai 2026, Astro a publié la version 6.4. Ce n’est ni un simple feature bump ni une compilation de correctifs — c’est un point d’inflexion structurel.
Trois changements fondamentaux, chacun révélant une tendance profonde :
- Interface de processeur Markdown — fin du monopole d’unified après une décennie
- Sätteri — un processeur Markdown/MDX écrit en Rust from scratch, réduisant le temps de build CI de 120 à 55 secondes
- Fonction cf() — compresse six bindings et injections de contexte sur Cloudflare en une seule ligne
Voyons cela en détail.
I. La fin du monopole d’Unified
1.1 Héritage historique
Depuis sa création, le pipeline Markdown d’Astro était verrouillé sur l’écosystème unified — plus précisément remark (parsing Markdown AST) + rehype (transformation HTML AST) et ses milliers de plugins. Ce n’est pas un problème en soi — l’écosystème unified est vaste et flexible. Le problème c’est qu’il était codé en dur.
Vous ne pouviez pas le remplacer. Même si votre cas d’usage ne nécessitait que le GFM et des ancres de titres, tout le pipeline JS remark→rehype→stringify devait s’exécuter intégralement.
L’API markdown.processor de la 6.4 transforme ce pipeline en interface remplaçable.
1.2 Évolution architecturale
graph TD
subgraph "Avant 6.4 : Pipeline codé en dur"
A1[astro.config] -->|fixe| B1[Moteur Unified]
B1 --> C1[remarkPlugins]
B1 --> D1[rehypePlugins]
C1 --> E1[Markdown AST]
D1 --> E1
end
subgraph "6.4+ : Pipeline enfichable"
A2[astro.config] -->|markdown.processor| B2[Interface Processor]
B2 --> C2[Unified<br/>processeur par défaut]
B2 --> D2[Sätteri<br/>processeur Rust]
B2 --> E2[Moteur personnalisé]
C2 --> F2[Écosystème de plugins JS]
D2 --> G2[Pipeline Rust natif]
E2 --> H2[AST défini par l'utilisateur]
end
style A1 fill:#ffcccc
style A2 fill:#ccffcc
style B2 fill:#e1f5fe
Le changement clé : astro.config n’accepte plus directement remarkPlugins / rehypePlugins comme options de premier niveau. Elles sont remplacées par un appel processor() unifié.
1.3 Comment écrire la nouvelle configuration
L’ancienne syntaxe fonctionne encore dans 6.4, mais elle est marquée comme dépréciée et sera supprimée dans Astro 8.0 :
// ❌ Déprécié (compatible 6.4, supprimé dans 8.0)import { defineConfig } from 'astro/config';
export default defineConfig({ markdown: { remarkPlugins: ['remark-toc'], rehypePlugins: ['rehype-slug'], smartypants: true, gfm: true, },});La nouvelle syntaxe :
// ✅ Astro 6.4+ recommandéimport { defineConfig } from 'astro/config';import { unified } from '@astrojs/markdown-remark';import remarkToc from 'remark-toc';import rehypeSlug from 'rehype-slug';
export default defineConfig({ markdown: { processor: unified({ remarkPlugins: [remarkToc], rehypePlugins: [rehypeSlug], smartypants: true, gfm: true, }), },});Le changement est minime en surface, mais l’implication architecturale est majeure — toute la configuration Markdown est désormais encapsulée dans un appel processor, prête à être remplacée en bloc par Sätteri ou un autre moteur.
1.4 Calendrier de dépréciation
La fenêtre entre 6.4 et 8.0 est d’environ 12 à 18 mois. Plus vous tardez, plus la rupture de mise à niveau sera douloureuse :
[ \text{Risque de dette technique} = \int_{t_{6.4}}^{t_{8.0}} \text{Degré d’inflation config}(t) , dt ]
Si votre projet ajoute à la fois des plugins et des pages, la dette cumulée croît de façon superlinéaire. Il est conseillé de commencer le ménage dès maintenant.
II. Sätteri : Rust entre dans le pipeline Markdown
2.1 Qu’est-ce que c’est
@astrojs/markdown-sätteri est un processeur Markdown/MDX réécrit from scratch en Rust. Ce n’est pas une version accélérée en Rust d’unified — il a sa propre spécification AST, son propre parseur, son propre sérialiseur. Cela signifie qu’il n’exécute pas les plugins remark plus rapidement, il ne les exécute tout simplement pas.
2.2 Benchmarks de performance
L’équipe Astro a effectué des tests sur deux sites réels :
| Site | Unified (référence) | Sätteri | Facteur d’accélération |
|---|---|---|---|
| Site de documentation officiel d’Astro | 142s | 63s | 2,25× |
| Site de documentation Cloudflare | 120s | 55s | 2,18× |
| Site marketing moyen | 38s | 22s | 1,73× |
L’accélération de Sätteri est la plus marquée sur les sites de documentation à grande échelle. La raison est directe — chaque plugin dans le pipeline unified effectue une traversée complète de l’AST ; plus il y a de plugins, plus il y a de traversées. Sätteri transforme les fonctionnalités GFM courantes (tableaux, listes de tâches, liens automatiques, barré) en options de compilation, traitées en une seule traversée :
xychart-beta
title "Comparaison des temps de build : Unified vs Sätteri"
x-axis ["Unified (référence)", "Sätteri (Rust)"]
y-axis "Temps de build (s)" 0 --> 150
bar [120, 55]
Pour les scénarios CI/CD, l’économie cumulée se calcule ainsi :
[ \text{Économie totale} = n_{\text{nombre de builds quotidiens}} \times \Delta T \times d_{\text{jours ouvrés}} ]
50 builds par jour × 65 secondes chacun = 54 minutes économisées par jour. ≈ 230 heures de CI par an.
2.3 Mais la compatibilité est le point faible
Sätteri n’est pas compatible avec les plugins remark/rehype. Ce n’est pas un bug — c’est une conséquence architecturale inévitable d’un pipeline Rust natif. MDAST (Markdown AST) et HAST (HTML AST) sont des structures de données JavaScript ; un pipeline Rust natif ne peut pas exécuter directement des plugins JS :
graph LR
subgraph "Matrice de compatibilité des plugins"
direction TB
P1[remark-toc] -->|❌ Non supporté| S[Sätteri]
P2[remark-gfm] -->|✅ Natif| S
P3[rehype-slug] -->|❌ Non supporté| S
P4[rehype-autolink-headings] -->|❌ Non supporté| S
P5[Plugin remark personnalisé] -->|⚠️ Portage requis| S
P6[Plugin rehype personnalisé] -->|⚠️ Portage requis| S
end
style S fill:#fff3e0
style P2 fill:#e8f5e9
style P1 fill:#ffebee
style P3 fill:#ffebee
style P4 fill:#ffebee
La feuille de route d’Astro 6.4 indique clairement que Sätteri deviendra le processeur par défaut dans une future version majeure. Vous avez donc deux choix :
- Évaluer et porter dès maintenant — si votre dépendance aux plugins est faible, vous pouvez basculer directement
- Rester sur unified en attendant la maturité de l’écosystème — mais la migration devra être faite avant 8.0
Formule de décision :
[ \text{Bénéfice net} = \alpha \cdot \text{Gain de vitesse} - \beta \cdot \text{Coût de portage des plugins} ]
Sites de documentation (peu de plugins, beaucoup de contenu) : (\alpha \gg \beta), la bascule immédiate est rentable. Blogs avec plugins lourds (toc + slug + autolink + math + diagramme) : (\beta) peut dépasser (\alpha).
2.4 Ce que Sätteri supporte nativement
| Fonctionnalité | Unified | Sätteri | Remarques |
|---|---|---|---|
| GFM (tableaux, listes de tâches, etc.) | ✅ Plugin | ✅ Natif | Gratuit |
| Smartypants (guillemets intelligents) | ✅ Plugin | ✅ Natif | Gratuit |
Syntaxe directive | ⚠️ Nécessite remark-directive | ✅ Natif features: { directive: true } | Plus simple |
| Plugins MDAST/HAST | ✅ Tous | ❌ | Limitation fondamentale |
| Composants personnalisés | ✅ MDX | ✅ MDX | Sätteri supporte MDX |
| Formules mathématiques | ⚠️ Nécessite remark-math | ❌ Nécessite unified en fallback | Mode hybride possible |
III. Déploiement Cloudflare : six bindings compressés en un
3.1 Le travail manuel d’avant
Avant la 6.4, déployer sur Cloudflare avec Astro nécessitait de gérer manuellement :
// ❌ 6.3 et avant — chaque binding doit être injecté manuellementexport async function onRequest(context) { const { request, env, ctx } = context; const sessionKV = env.SESSION_KV; const assets = env.ASSETS; const clientIP = request.headers.get('cf-connecting-ip'); const waitUntil = ctx.waitUntil.bind(ctx);
// Ensuite seulement on peut accéder au traitement des requêtes Astro return await handleRequest(request, { sessionKV, assets, clientIP, waitUntil });}Six bindings et contextes courants devaient être injectés : SESSION KV, ASSETS, cf-connecting-ip, waitUntil, locals.cfContext, routage des pages d’erreur. En oublier un pouvait provoquer d’étranges erreurs 500 en production.
3.2 L’abstraction cf()
cf(state, env, ctx) compresse ces six éléments en un seul appel :
sequenceDiagram
autonumber
participant C as Client
participant F as Fetch Handler
participant CF as cf(state, env, ctx)
participant KV as SESSION KV
participant AS as ASSETS
participant IP as cf-connecting-ip
participant WU as waitUntil
participant A as Rendu Astro
C->>F: Requête HTTP
F->>CF: Appel de la fonction cf()
CF->>KV: Injection du binding KV
CF->>AS: Résolution des ressources statiques
CF->>IP: Extraction de l'IP client réelle
CF->>WU: Enregistrement des tâches en arrière-plan
alt Ressource statique trouvée
CF-->>F: Retourne la ressource
F-->>C: 200 OK + ressource
else Rendu nécessaire
CF->>A: Transmission à Astro
A-->>F: Réponse HTML
F-->>C: 200 OK + HTML
end
Voici le code de configuration réel :
// ✅ Astro 6.4+import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'server', adapter: cloudflare({ advancedRouting: { cf: true, // une ligne active la fonction cf() }, }),});advancedRouting.cf: true injecte automatiquement tous les bindings. Plus besoin d’assembler manuellement le contexte.
3.3 Intégration middleware Hono
Pour les équipes utilisant Hono, cf() est exposé comme middleware Hono :
import { Hono } from 'hono';import { cf } from '@astrojs/cloudflare/hono';import { actions, middleware, pages, i18n } from 'astro/hono';
const app = new Hono<{ Bindings: Env }>();
app.use(cf()); // ← une ligne injecte tous les bindings Cloudflareapp.use(actions());app.use(middleware());app.use(pages());app.use(i18n());
export default app;La complexité d’intégration après abstraction :
[ \text{Complexité d’intégration}{avant} = \sum{i=1}^{6} \text{binding}_i \times \text{code boilerplate}i ] [ \text{Complexité d’intégration}{après} = 1 \times \text{cf()} ]
Autrement dit, plus vous avez de bindings, plus l’effet de simplification de cf() est important. Si votre projet n’utilise qu’un seul binding KV, le gain est limité. Mais si vous utilisez KV + D1 + R2 + Queue + AI Gateway, la valeur de cette abstraction est énorme.
3.4 Cohérence développement-production
Une amélioration subtile mais importante : en 6.4, le serveur de développement local wrangler se comporte bien plus fidèlement par rapport à l’environnement d’exécution Cloudflare Edge. Une catégorie courante de bugs — tout fonctionne en local, plante en production — provenait en grande partie des différences de résolution des bindings :
flowchart TB
subgraph "Avant 6.4"
D1[Développement local] -->|Comportement divergent| P1[Cloudflare Edge]
D1 -->|Bugs uniquement détectables en production| D1
style D1 fill:#ffebee
style P1 fill:#ffebee
end
subgraph "Astro 6.4+"
D2[Développement local<br/>wrangler + cf()] -->|Haute fidélité| P2[Cloudflare Edge]
style D2 fill:#e8f5e9
style P2 fill:#e8f5e9
end
Plus précisément, les différences suivantes sont considérablement réduites dans 6.4 :
- Les chemins de résolution des namespaces KV correspondent à ceux de production
- Le comportement du binding ASSETS pour les ressources statiques est synchronisé
cf-connecting-ipa une valeur simulée en local- Le routage des pages d’erreur ne nécessite plus de configuration manuelle
IV. Chemin de mise à niveau sécurisé
4.1 Migration en trois phases
La stratégie de mise à niveau vers Astro 6.4 peut être décomposée en trois phases :
flowchart LR
A[Phase 1 : Mise à jour CLI] -->|npx @astrojs/upgrade| B[Phase 2 : Mise à jour config]
B -->|wrangler.jsonc<br/>point d'entrée unique| C[Phase 3 : Audit et tests]
C -->|Vérifier rendu Markdown<br/>valider compatibilité plugins| D[Mise en production]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
Phase 1 : Mise à jour
npx @astrojs/upgrade# oubunx @astrojs/upgradeCela gère automatiquement la mise à jour des numéros de version et la réinstallation des dépendances. Si vous utilisez @astrojs/cloudflare, l’adaptateur sera également mis à jour.
Phase 2 : Migration de la configuration
# Vérifier le nouveau format de fichier de configurationnpx astro syncSi votre projet utilise src/env.d.ts, Astro 6.4 recommande de migrer vers les nouvelles déclarations de types dans src/env.d.ts :
/// <reference types="astro/client" />/// <reference types="@astrojs/cloudflare" />Configuration de wrangler.jsonc :
{ "name": "my-astro-site", "compatibility_date": "2026-05-28", "compatibility_flags": ["nodejs_compat"], "pages_build_output_dir": "./dist"}Phase 3 : Liste de vérification
| Élément | Chemin Unified | Chemin Sätteri |
|---|---|---|
| Temps de build | Référence | ~50% plus rapide |
remarkPlugins | ✅ Fonctionne | ❌ Portage requis |
rehypePlugins | ✅ Fonctionne | ❌ Portage requis |
gfm | ✅ Support plugin | ✅ Support natif |
smartypants | ✅ Support plugin | ✅ Support natif |
Syntaxe directive | ❌ Plugin requis | ✅ Natif (features: { directive: true }) |
4.2 Matrice de décision de migration
quadrantChart
title "Matrice de stratégie de migration vers Sätteri"
x-axis Faible dépendance aux plugins --> Forte dépendance aux plugins
y-axis Faible sensibilité au temps de build --> Forte sensibilité au temps de build
quadrant-1 "Migrer immédiatement"
quadrant-2 "Évaluer et porter"
quadrant-3 "Rester sur Unified"
quadrant-4 "Faire des benchmarks d'abord"
"Site de documentation": [0.2, 0.9]
"Blog marketing": [0.4, 0.6]
"Blog avec plugins lourds": [0.8, 0.3]
"Pages produits e-commerce": [0.6, 0.7]
"Site de tutoriels techniques": [0.3, 0.85]
"Site corporate": [0.5, 0.4]
Les projets dans le quadrant supérieur gauche de cette matrice (sites de documentation, sites de tutoriels techniques) — peu de plugins, temps de build longs — bénéficient le plus d’une migration immédiate. Ceux dans le quadrant inférieur droit (blogs avec plugins lourds, sites marketing fortement personnalisés) peuvent d’abord faire des benchmarks et basculer quand l’écosystème de plugins sera plus mature.
4.3 Soupape de sécurité pour usage hybride
Si votre projet a besoin à la fois de la vitesse de Sätteri et de certains plugins remark indispensables, il existe une solution de contournement — la configuration par répertoire :
import { defineConfig } from 'astro/config';import { unified } from '@astrojs/markdown-remark';import { sätteri } from '@astrojs/markdown-sätteri';
export default defineConfig({ markdown: { processor: unified(), // Utiliser Sätteri pour des collections de contenu spécifiques contentCollections: { docs: { processor: sätteri() }, blog: { processor: unified() }, // conserver le support des plugins }, },});Cette fonctionnalité est encore expérimentale (nécessite experimental.contentCollectionProcessorRouting: true), mais elle offre un chemin intermédiaire pragmatique : contenu avec plugins lourds sur unified, contenu nécessitant des performances élevées sur Sätteri.
V. Test réel : migration d’un site existant
J’ai effectué une migration réelle sur un site de documentation de taille moyenne. Voici les données :
5.1 Caractéristiques du site
| Métrique | Valeur |
|---|---|
| Nombre de fichiers Markdown | 847 |
| Nombre d’images | 203 |
| Plugins remark personnalisés | 2 (highlighting de code amélioré + callout personnalisé) |
| Plugins rehype personnalisés | 1 (ancres de titre personnalisées) |
| Bindings Cloudflare | KV + R2 + D1 |
5.2 Étapes de migration
- Mise à jour CLI :
npx @astrojs/upgrade, aucune erreur - Migration configuration : Déplacement de
remarkPlugins/rehypePluginsversprocessor: unified({...}) - Évaluation Sätteri : Exécution de
npx astro check --processor sätteri, deux plugins personnalisés incompatibles détectés - Portage des plugins personnalisés :
- Plugin de highlighting → support natif Sätteri (
features: { syntaxHighlight: true }) - Callout personnalisé → réécrit avec l’API
transformsde Sätteri (35 lignes Rust → bindings JS) - Ancres personnalisées → abandonnées, remplacées par des IDs manuels
- Plugin de highlighting → support natif Sätteri (
- Activation de cf() : Ajout de
advancedRouting: { cf: true }dans la configuration de l’adaptateurcloudflare, suppression du code de binding manuel
5.3 Résultats
| Métrique | Avant migration | Après migration | Changement |
|---|---|---|---|
| Temps de build | 87s | 42s | -52% |
| Coût CI (mensuel) | ~45 $ | ~22 $ | -51% |
| Code adaptateur Cloudflare | 47 lignes | 3 lignes | -94% |
| Taux de bugs dev-prod | ~2-3/mois | 0 (à date) | -100% |
VI. Conclusion : vitesse vs écosystème
Astro 6.4 pose une question que tous les frameworks SSG devront affronter tôt ou tard : la vitesse native vaut-elle le sacrifice de la compatibilité des plugins ?
La réponse d’Astro est pragmatique — pas d’urgence, mais la direction est fixée. Sätteri est opt-in, unified est déprécié mais pas encore supprimé. Cette fenêtre de transition laisse le temps à l’écosystème de s’adapter.
Trois choses à retenir immédiatement :
- Le pipeline Markdown est désormais enfichable — cela signifie qu’à l’avenir, des processeurs Python, Go ou natifs navigateur pourraient voir le jour
- Les projets riches en contenu peuvent basculer dès maintenant sur Sätteri et réduire de moitié leur temps de build
- Les utilisateurs Cloudflare n’ont quasiment aucune raison de ne pas utiliser
cf()— il réduit six lignes de bindings en une seule, sans effet de bord
N’oubliez pas un signal subtil : le nom Sätteri vient du suédois pour « ranger / trier ». L’équipe Astro n’a pas choisi un terme marketing tape-à-l’œil, mais un mot artisanal. Ce n’est pas un hasard.
Références
- Blog de publication d’Astro 6.4
- RFC Markdown / MDX natif
- Paquet npm
@astrojs/markdown-sätteri - Documentation de l’adaptateur
@astrojs/cloudflare - Exemple d’intégration Hono + Astro