needhelp
← Retour au blog

Astro 6.4 en profondeur : pipeline Markdown enfichable, Sätteri propulsé par Rust et révolution du déploiement Cloudflare

par needhelp
Astro
Frontend
Rust
Cloudflare
Markdown
SSG
Développement Web

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 :

SiteUnified (référence)SätteriFacteur d’accélération
Site de documentation officiel d’Astro142s63s2,25×
Site de documentation Cloudflare120s55s2,18×
Site marketing moyen38s22s1,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 :

  1. Évaluer et porter dès maintenant — si votre dépendance aux plugins est faible, vous pouvez basculer directement
  2. 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éUnifiedSätteriRemarques
GFM (tableaux, listes de tâches, etc.)✅ Plugin✅ NatifGratuit
Smartypants (guillemets intelligents)✅ Plugin✅ NatifGratuit
Syntaxe directive⚠️ Nécessite remark-directive✅ Natif features: { directive: true }Plus simple
Plugins MDAST/HAST✅ TousLimitation fondamentale
Composants personnalisés✅ MDX✅ MDXSätteri supporte MDX
Formules mathématiques⚠️ Nécessite remark-math❌ Nécessite unified en fallbackMode 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é manuellement
export 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 Cloudflare
app.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-ip a 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

Terminal window
npx @astrojs/upgrade
# ou
bunx @astrojs/upgrade

Cela 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

Terminal window
# Vérifier le nouveau format de fichier de configuration
npx astro sync

Si 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émentChemin UnifiedChemin Sätteri
Temps de buildRé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étriqueValeur
Nombre de fichiers Markdown847
Nombre d’images203
Plugins remark personnalisés2 (highlighting de code amélioré + callout personnalisé)
Plugins rehype personnalisés1 (ancres de titre personnalisées)
Bindings CloudflareKV + R2 + D1

5.2 Étapes de migration

  1. Mise à jour CLI : npx @astrojs/upgrade, aucune erreur
  2. Migration configuration : Déplacement de remarkPlugins / rehypePlugins vers processor: unified({...})
  3. Évaluation Sätteri : Exécution de npx astro check --processor sätteri, deux plugins personnalisés incompatibles détectés
  4. Portage des plugins personnalisés :
    • Plugin de highlighting → support natif Sätteri (features: { syntaxHighlight: true })
    • Callout personnalisé → réécrit avec l’API transforms de Sätteri (35 lignes Rust → bindings JS)
    • Ancres personnalisées → abandonnées, remplacées par des IDs manuels
  5. Activation de cf() : Ajout de advancedRouting: { cf: true } dans la configuration de l’adaptateur cloudflare, suppression du code de binding manuel

5.3 Résultats

MétriqueAvant migrationAprès migrationChangement
Temps de build87s42s-52%
Coût CI (mensuel)~45 $~22 $-51%
Code adaptateur Cloudflare47 lignes3 lignes-94%
Taux de bugs dev-prod~2-3/mois0 (à 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 :

  1. Le pipeline Markdown est désormais enfichable — cela signifie qu’à l’avenir, des processeurs Python, Go ou natifs navigateur pourraient voir le jour
  2. Les projets riches en contenu peuvent basculer dès maintenant sur Sätteri et réduire de moitié leur temps de build
  3. 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

Partager cette page