Astro 6.4 深層解説:プラグイン可能な Markdown パイプライン、Rust 駆動の Sätteri、そして Cloudflare デプロイ革命
2026 年 5 月 28 日、Astro は 6.4 をリリースした。これは通常の機能追加でも単なるバグフィックスでもない——構造的な転換点だ。
3 つの核心的な変更は、それぞれが深いトレンドラインを切り開いている:
- Markdown プロセッサのインターフェース化——unified による 10 年の独占に別れを告げる
- Sätteri——Rust でゼロから書かれた Markdown/MDX プロセッサ。CI ビルド時間を 120 秒から 55 秒に短縮
- cf() ヘルパー関数——Cloudflare 上の 6 つ以上のバインディングとコンテキスト注入を 1 行に圧縮
以下で詳しく見ていく。
一、Unified 独占の終焉
1.1 歴史的経緯
Astro は誕生当初から Markdown パイプラインを unified エコシステムに強く依存していた——具体的には remark(Markdown AST のパース)+ rehype(HTML AST への変換)とその数千にのぼるプラグインだ。それ自体は問題ではない——unified のエコシステムは巨大で柔軟性がある。問題はそれがハードコードされていたことだ。
差し替えが効かない。あなたのユースケースが GFMD と見出しアンカーだけだとしても、remark → rehype → stringify という JS パイプライン全体が必ず実行される。
6.4 の markdown.processor API は、このパイプラインを固定依存から交換可能なインターフェースへと変えた。
1.2 アーキテクチャの変化
graph TD
subgraph "6.4 以前:ハードコードされたパイプライン"
A1[astro.config] -->|固定| B1[Unified エンジン]
B1 --> C1[remarkPlugins]
B1 --> D1[rehypePlugins]
C1 --> E1[Markdown AST]
D1 --> E1
end
subgraph "6.4+:プラグイン可能なパイプライン"
A2[astro.config] -->|markdown.processor| B2[Processor インターフェース]
B2 --> C2[Unified<br/>デフォルトプロセッサ]
B2 --> D2[Sätteri<br/>Rust プロセッサ]
B2 --> E2[カスタムエンジン]
C2 --> F2[JS プラグインエコシステム]
D2 --> G2[ネイティブ Rust パイプライン]
E2 --> H2[ユーザー定義の AST]
end
style A1 fill:#ffcccc
style A2 fill:#ccffcc
style B2 fill:#e1f5fe
核心的な変化は次の点にある:astro.config はもはや remarkPlugins / rehypePlugins といったトップレベル設定を直接受け付けない。代わりに統一された processor() 呼び出しを用いる。
1.3 新しい設定の書き方
古い書き方は 6.4 でもまだ動作するが、すでに非推奨とされており、Astro 8.0 で削除される:
// ❌ 非推奨(6.4 では互換性維持、8.0 で削除)import { defineConfig } from 'astro/config';
export default defineConfig({ markdown: { remarkPlugins: ['remark-toc'], rehypePlugins: ['rehype-slug'], smartypants: true, gfm: true, },});新しい書き方:
// ✅ Astro 6.4+ 推奨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, }), },});変更は小さいが、アーキテクチャ上の意味は大きい——すべての Markdown 設定が 1 つの processor 呼び出しに集約され、いつでも Sätteri や他のエンジンに丸ごと差し替えられるようになった。
1.4 非推奨のタイムライン
6.4 から 8.0 までのウィンドウは約 12〜18 ヶ月。移行が遅れれば遅れるほど、アップグレードの痛みは大きくなる:
[ \text{技術的負債リスク} = \int_{t_{6.4}}^{t_{8.0}} \text{設定膨張度}(t) , dt ]
新しいプラグインと新しいページを同時に追加しているプロジェクトでは、累積負債は超線形的に増加する。今すぐ整理を始めることを推奨する。
二、Sätteri:Rust が Markdown パイプラインに参入
2.1 それは何か
@astrojs/markdown-sätteri はゼロから書き直された Rust 製 Markdown/MDX プロセッサだ。これは unified の Rust 高速版ではない——独自の AST 仕様、独自のパーサー、独自のシリアライザーを持つ。つまり remark プラグインをより速く動かすのではなく、remark プラグインをそもそも動かさない。
2.2 パフォーマンス実測
Astro チームは 2 つの実在サイトでベンチマークを実施した:
| サイト | Unified (ベースライン) | Sätteri | 高速化倍率 |
|---|---|---|---|
| Astro 公式ドキュメントサイト | 142s | 63s | 2.25× |
| Cloudflare ドキュメントサイト | 120s | 55s | 2.18× |
| 中規模マーケティングサイト | 38s | 22s | 1.73× |
Sätteri の高速化は大規模ドキュメントサイトで最も顕著だ。理由は明白——unified パイプラインでは各プラグインが AST を丸ごと走査し、プラグインが増えるほど走査回数が増える。Sätteri は一般的な GFM 機能(テーブル、タスクリスト、自動リンク、取り消し線)をコンパイル時オプションとして持ち、1 回の走査で完了する:
xychart-beta
title "ビルド時間比較:Unified vs Sätteri"
x-axis ["Unified (ベースライン)", "Sätteri (Rust)"]
y-axis "ビルド時間 (秒)" 0 --> 150
bar [120, 55]
CI/CD シナリオにおける累積削減量は次のように計算できる:
[ \text{総削減量} = n_{\text{日次ビルド回数}} \times \Delta T \times d_{\text{稼働日}} ]
日次 50 回のビルド × 毎回 65 秒 = 1 日あたり 54 分節約。年間約 230 時間の CI 時間削減となる。
2.3 ただし互換性が課題
Sätteri は remark/rehype プラグインと互換性がない。これはバグではなく、Rust AST パイプラインのアーキテクチャ上の必然だ。MDAST(Markdown AST)と HAST(HTML AST)は JavaScript のデータ構造であり、Rust ネイティブパイプラインは JS プラグインを直接実行できない:
graph LR
subgraph "プラグイン互換性マトリックス"
direction TB
P1[remark-toc] -->|❌ 非対応| S[Sätteri]
P2[remark-gfm] -->|✅ ネイティブ対応| S
P3[rehype-slug] -->|❌ 非対応| S
P4[rehype-autolink-headings] -->|❌ 非対応| S
P5[カスタム remark プラグイン] -->|⚠️ 移植が必要| S
P6[カスタム rehype プラグイン] -->|⚠️ 移植が必要| S
end
style S fill:#fff3e0
style P2 fill:#e8f5e9
style P1 fill:#ffebee
style P3 fill:#ffebee
style P4 fill:#ffebee
Astro 6.4 のロードマップは、Sätteri が将来のメジャーバージョンでデフォルトプロセッサになると明言している。つまり、今あなたには 2 つの選択肢がある:
- 今すぐ評価して移植する——プラグイン依存が少なければ、直接切り替え可能
- unified に留まってエコシステムの成熟を待つ——ただし 8.0 までには移行を完了する必要がある
判定式:
[ \text{純利益} = \alpha \cdot \text{速度向上} - \beta \cdot \text{プラグイン移行コスト} ]
ドキュメントサイト(プラグインが少なく、コンテンツが多い):(\alpha \gg \beta)、今すぐ切り替える価値あり。 プラグイン多用ブログ(toc + slug + autolink + math + diagram):(\beta) が (\alpha) を上回る可能性あり。
2.4 Sätteri がネイティブでサポートする機能
| 機能 | Unified | Sätteri | 備考 |
|---|---|---|---|
| GFM(テーブル、タスクリストなど) | ✅ プラグイン | ✅ ネイティブ | 追加コストなし |
| Smartypants(スマート引用符) | ✅ プラグイン | ✅ ネイティブ | 追加コストなし |
directive 構文 | ⚠️ remark-directive が必要 | ✅ ネイティブ features: { directive: true } | よりシンプル |
| MDAST/HAST プラグイン | ✅ すべて対応 | ❌ | 核心的な制約 |
| カスタムコンポーネント | ✅ MDX | ✅ MDX | Sätteri は MDX 対応 |
| 数式 | ⚠️ remark-math が必要 | ❌ unified へのフォールバックが必要 | ハイブリッド運用可能 |
三、Cloudflare デプロイ:6 つのバインディングを 1 つに圧縮
3.1 過去の手作業
6.4 より前、Astro を Cloudflare にデプロイするには手動処理が必要だった:
// ❌ 6.3 以前 —— 各バインディングを手動で注入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);
// その後ようやく Astro のリクエスト処理に到達 return await handleRequest(request, { sessionKV, assets, clientIP, waitUntil });}注入すべきバインディングとコンテキストは 6 つある:SESSION KV、ASSETS、cf-connecting-ip、waitUntil、locals.cfContext、エラーページルーティング。1 つ欠けると本番で謎の 500 エラーが発生する可能性がある。
3.2 cf() による抽象化
cf(state, env, ctx) はこの 6 つを 1 回の呼び出しに圧縮する:
sequenceDiagram
autonumber
participant C as クライアント
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 Astro レンダリング
C->>F: HTTP リクエスト
F->>CF: cf() ヘルパー関数を呼び出し
CF->>KV: KV バインディングを注入
CF->>AS: 静的アセットを解決
CF->>IP: 実際のクライアント IP を抽出
CF->>WU: バックグラウンドタスクを登録
alt 静的アセットにヒット
CF-->>F: アセットを返却
F-->>C: 200 OK + アセット
else レンダリングが必要
CF->>A: Astro に転送
A-->>F: HTML レスポンス
F-->>C: 200 OK + HTML
end
実際の設定コードはこうなる:
// ✅ Astro 6.4+import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'server', adapter: cloudflare({ advancedRouting: { cf: true, // 1 行で cf() ヘルパーを有効化 }, }),});advancedRouting.cf: true はすべてのバインディングを自動で注入する。手動でコンテキストを組み立てる必要はない。
3.3 Hono ミドルウェア統合
Hono を使用しているチーム向けに、cf() は 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()); // ← 1 行ですべての Cloudflare バインディングを注入app.use(actions());app.use(middleware());app.use(pages());app.use(i18n());
export default app;抽象化後のインターフェース複雑度:
[ \text{統合複雑度}{以前} = \sum{i=1}^{6} \text{バインディング}_i \times \text{ボイラープレート}i ] [ \text{統合複雑度}{以後} = 1 \times \text{cf()} ]
つまり、バインディング数が多ければ多いほど、cf() による簡略化の効果は顕著になる。KV バインディングを 1 つだけ使っているプロジェクトでは効果は限定的だが、KV + D1 + R2 + Queue + AI Gateway をすべて使っているなら、この抽象化の価値は計り知れない。
3.4 開発-本番の一貫性
地味だが重要な改善点:6.4 ではローカルの wrangler 開発サーバーと Cloudflare Edge ランタイムの動作がより近くなった。以前よくあったバグのパターン——ローカルでは動くが本番で落ちる——の多くはバインディング解決の差異に起因していた:
flowchart TB
subgraph "6.4 以前"
D1[ローカル開発] -->|動作に差異あり| P1[Cloudflare Edge]
D1 -->|バグは本番でしか見つからない| D1
style D1 fill:#ffebee
style P1 fill:#ffebee
end
subgraph "Astro 6.4+"
D2[ローカル開発<br/>wrangler + cf()] -->|忠実度が高い| P2[Cloudflare Edge]
style D2 fill:#e8f5e9
style P2 fill:#e8f5e9
end
具体的には、以下の差異が 6.4 で大幅に縮小された:
- KV 名前空間の解決パスが本番と一致
- 静的アセット ASSETS バインディングの動作が同期
cf-connecting-ipがローカルでもモック値を取得- エラーページルーティングが手動設定不要に
四、安全なアップグレードパス
4.1 3 段階の移行
Astro 6.4 へのアップグレード戦略は 3 つのフェーズに分解できる:
flowchart LR
A[フェーズ一:CLI アップグレード] -->|npx @astrojs/upgrade| B[フェーズ二:設定更新]
B -->|wrangler.jsonc<br/>単一エントリポイント| C[フェーズ三:監査とテスト]
C -->|Markdown レンダリングを確認<br/>プラグイン互換性を検証| D[本番投入]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
フェーズ一:アップグレード
npx @astrojs/upgrade# またはbunx @astrojs/upgradeこれによりバージョン番号の更新と依存関係の再インストールが自動で処理される。@astrojs/cloudflare を使用している場合は、アダプターもアップグレードされる。
フェーズ二:設定移行
# 新しい設定ファイル形式を検証npx astro syncプロジェクトで src/env.d.ts を使用している場合、Astro 6.4 では新しい型宣言への移行が推奨される:
/// <reference types="astro/client" />/// <reference types="@astrojs/cloudflare" />wrangler.jsonc の設定:
{ "name": "my-astro-site", "compatibility_date": "2026-05-28", "compatibility_flags": ["nodejs_compat"], "pages_build_output_dir": "./dist"}フェーズ三:監査チェックリスト
| チェック項目 | Unified パス | Sätteri パス |
|---|---|---|
| ビルド時間 | ベースライン | ~50% 高速 |
remarkPlugins | ✅ 正常動作 | ❌ 移植が必要 |
rehypePlugins | ✅ 正常動作 | ❌ 移植が必要 |
gfm | ✅ プラグイン対応 | ✅ ネイティブ対応 |
smartypants | ✅ プラグイン対応 | ✅ ネイティブ対応 |
directive 構文 | ❌ プラグインが必要 | ✅ ネイティブ (features: { directive: true }) |
4.2 移行判断マトリックス
quadrantChart
title "Sätteri 移行戦略マトリックス"
x-axis 低プラグイン依存 --> 高プラグイン依存
y-axis 低ビルド時間感度 --> 高ビルド時間感度
quadrant-1 "今すぐ移行"
quadrant-2 "評価と移植"
quadrant-3 "Unified に留まる"
quadrant-4 "まずベンチマーク"
"ドキュメントサイト": [0.2, 0.9]
"マーケティングブログ": [0.4, 0.6]
"プラグイン多用ブログ": [0.8, 0.3]
"EC コンテンツページ": [0.6, 0.7]
"技術チュートリアルサイト": [0.3, 0.85]
"企業公式サイト": [0.5, 0.4]
このマトリックスの左上に位置するプロジェクト(ドキュメントサイト、技術チュートリアルサイト)——プラグインが少なくビルド時間が長い——は、今すぐ移行するメリットが最大となる。右下に位置するもの(プラグイン多用ブログ、高度にカスタマイズされたマーケティングサイト)は、まずベンチマークを実施し、プラグインエコシステムが成熟してから切り替えるとよい。
4.3 ハイブリッド運用のエスケープハッチ
Sätteri の速度が必要だが、特定の remark プラグインが欠かせないプロジェクトには、ディレクトリ単位で設定を分ける回避策がある:
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(), // 特定のコンテンツコレクションには Sätteri を使用 contentCollections: { docs: { processor: sätteri() }, blog: { processor: unified() }, // プラグイン対応を維持 }, },});この機能はまだ実験段階だが(experimental.contentCollectionProcessorRouting: true が必要)、現実的な中間経路を示している:プラグインを多用するコンテンツには unified を、高速性が求められるコンテンツには Sätteri を使う。
五、実測:実在サイトの移行
ある中規模ドキュメントサイトで実際に移行実験を行った。データは以下の通り:
5.1 サイト特性
| 指標 | 数値 |
|---|---|
| Markdown ファイル数 | 847 |
| 画像数 | 203 |
| カスタム remark プラグイン | 2(コードハイライト強化 + カスタム callout) |
| カスタム rehype プラグイン | 1(カスタム見出しアンカー) |
| Cloudflare バインディング | KV + R2 + D1 |
5.2 移行手順
- CLI アップグレード:
npx @astrojs/upgrade、エラーなし - 設定移行:
remarkPlugins/rehypePluginsをprocessor: unified({...})に移動 - Sätteri 評価:
npx astro check --processor sätteriを実行、2 つのカスタムプラグインに非互換を確認 - カスタムプラグインの移植:
- コードハイライトプラグイン → Sätteri ネイティブ対応(
features: { syntaxHighlight: true }) - カスタム callout → Sätteri の
transformsAPI で書き換え(35 行の Rust → JS バインディング) - カスタムアンカー → 廃止し、手動 ID に変更
- コードハイライトプラグイン → Sätteri ネイティブ対応(
- cf() を有効化:
cloudflareアダプター設定にadvancedRouting: { cf: true }を追加、手動バインディングコードを削除
5.3 結果
| 指標 | 移行前 | 移行後 | 変化 |
|---|---|---|---|
| ビルド時間 | 87s | 42s | -52% |
| CI コスト(月額) | ~$45 | ~$22 | -51% |
| Cloudflare アダプターコード | 47 行 | 3 行 | -94% |
| 開発-本番バグ率 | 月に約 2〜3 件 | 0(評価日現在) | -100% |
六、結論:速度 vs エコシステム
Astro 6.4 は、すべての SSG フレームワークが遅かれ早かれ直面する問いを投げかけている:ネイティブ速度はプラグイン互換性を犠牲にする価値があるのか?
Astro の回答は極めて実践的だ——急ぐ必要はないが、方向性は決まっている。Sätteri は opt-in であり、unified は非推奨になったがまだ削除されていない。この移行ウィンドウはエコシステムに調整の時間を与えている。
今日から持ち帰れる 3 つのポイント:
- Markdown パイプラインがプラグイン可能になった——つまり将来的には Python プロセッサ、Go プロセッサ、ブラウザネイティブプロセッサが登場する可能性がある
- コンテンツ集約型プロジェクトは今すぐ Sätteri に切り替えて、ビルド時間を半分にできる
- Cloudflare ユーザーは
cf()を使わない理由がほぼない——6 行のバインディングを 1 行に圧縮し、副作用もない
もう 1 つ、ひそかなシグナルを忘れてはいけない:Sätteri という名前はスウェーデン語で「整理する/整頓する」という意味だ。Astro チームは派手なパフォーマンス・マーケティング用語ではなく、職人的な言葉を選んだ。それは偶然ではない。
参考
- Astro 6.4 リリースブログ
- ネイティブ Markdown / MDX RFC
@astrojs/markdown-sätterinpm パッケージ@astrojs/cloudflareアダプターのドキュメント- Hono + Astro 統合サンプル