Astro 6.4: разбор — подключаемый Markdown-пайплайн, Sätteri на Rust и революция в деплое на Cloudflare
28 мая 2026 года Astro выпустил версию 6.4. Это не рядовое добавление фич и не простой набор багфиксов — это структурная точка перегиба.
Три ключевых изменения, каждое из которых вскрывает глубокий тренд:
- Интерфейсификация Markdown-процессора — конец десятилетней монополии unified
- Sätteri — процессор Markdown/MDX, написанный на Rust с нуля, сокращающий время CI-сборки со 120 до 55 секунд
- Вспомогательная функция
cf()— сжимающая 6+ биндингов и контекстов Cloudflare в одну строку
Разберём по порядку.
1. Конец монополии unified
1.1 Историческое наследие
С самого первого дня Markdown-пайплайн Astro был жёстко привязан к экосистеме unified — а именно к remark (парсинг Markdown AST) + rehype (преобразование HTML AST) и тысячам их плагинов. Сама по себе это не проблема — экосистема unified огромна и гибка. Проблема в том, что она была зашита намертво.
Заменить её было нельзя. Даже если вам нужны только GFM и якоря заголовков, весь JS-пайплайн remark → rehype → stringify должен был выполняться целиком.
API markdown.processor в версии 6.4 превращает этот пайплайн из жёсткой зависимости в сменный интерфейс.
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[Интерфейс процессора]
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 собрана в одном вызове processor, и в любой момент её можно целиком заменить на Sätteri или другой движок.
1.4 График устаревания
Окно перехода с 6.4 до 8.0 составляет примерно 12–18 месяцев. Чем позже миграция, тем болезненнее будет разрыв при обновлении:
[ \text{Риск техдолга} = \int_{t_{6.4}}^{t_{8.0}} \text{Степень разбухания конфигурации}(t) , dt ]
Если ваш проект одновременно добавляет новые плагины и новые страницы, совокупный долг будет расти сверхлинейно. Рекомендуется начать расчистку уже сейчас.
2. Sätteri: Rust в Markdown-пайплайне
2.1 Что это такое
@astrojs/markdown-sätteri — это переписанный с нуля процессор Markdown/MDX на Rust. Это не ускоренная версия unified на Rust — у него своя спецификация AST, свой парсер, свой сериализатор. Это значит, что он не просто выполняет remark-плагины быстрее — он их вообще не выполняет.
2.2 Реальная производительность
Команда Astro провела бенчмарки на двух реальных сайтах:
| Сайт | Unified (базовый) | Sätteri | Ускорение |
|---|---|---|---|
| Официальная документация Astro | 142 с | 63 с | 2,25× |
| Документация Cloudflare | 120 с | 55 с | 2,18× |
| Средний маркетинговый сайт | 38 с | 22 с | 1,73× |
Ускорение Sätteri наиболее заметно на крупных сайтах документации. Причина проста — каждый плагин в пайплайне unified выполняет полный обход AST; чем больше плагинов, тем больше проходов. Sätteri превращает типовые возможности GFM (таблицы, списки задач, автокорректировка ссылок, зачёркивание) в опции на этапе компиляции, выполняя всё за один проход:
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 секунд каждая = экономия 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 станет процессором по умолчанию в одном из будущих мажорных релизов. Это означает, что у вас есть два варианта:
- Оценить и портировать сейчас — если плагинов мало, можно переключиться сразу
- Остаться на 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 | Возможен гибридный режим |
3. Деплой на Cloudflare: шесть биндингов в одном
3.1 Ручной труд в прошлом
До версии 6.4 деплой на Cloudflare в Astro требовал ручной обработки:
// ❌ 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 });}Существует шесть типовых биндингов и контекстов, которые требуется инжектировать: SESSION KV, ASSETS, cf-connecting-ip, waitUntil, locals.cfContext, маршрутизация страниц ошибок. Если пропустить хотя бы один — можно получить неожиданную 500-ю ошибку в проде.
3.2 Абстракция cf()
cf(state, env, ctx) сжимает все шесть в один вызов:
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, // одна строка включает 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()); // ← одна строка — все биндинги Cloudflareapp.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-биндинг, выгода невелика. Но если в ходу KV + D1 + R2 + Queue + AI Gateway — ценность этой абстракции огромна.
3.4 Согласованность разработки и продакшена
Одно незаметное, но важное улучшение: в 6.4 локальный dev-сервер wrangler стал значительно ближе к поведению Cloudflare Edge Runtime. Распространённый класс багов — «локально работает, на проде упало» — во многом возникал из-за различий в разрешении биндингов:
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. Безопасный путь обновления
4.1 Три этапа миграции
Стратегию обновления до Astro 6.4 можно разбить на три этапа:
flowchart LR
A[Этап 1: Обновление CLI] -->|npx @astrojs/upgrade| B[Этап 2: Обновление конфигурации]
B -->|wrangler.jsonc<br/>единая точка входа| C[Этап 3: Аудит и тестирование]
C -->|Проверка рендеринга Markdown<br/>валидация совместимости плагинов| D[Запуск в продакшен]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
Этап 1: Обновление
npx @astrojs/upgrade# илиbunx @astrojs/upgradeЭто автоматически обработает обновление версий и переустановку зависимостей. Если вы используете @astrojs/cloudflare, адаптер также будет обновлён.
Этап 2: Миграция конфигурации
# Проверка нового формата конфигурацииnpx astro syncЕсли в вашем проекте используется src/env.d.ts, Astro 6.4 рекомендует перейти на новый тип объявления в src/env.d.ts:
/// <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"}Этап 3: Чек-лист аудита
| Проверка | Путь 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]
"Товарные страницы": [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. Реальный опыт: миграция живого сайта
Я провёл эксперимент по миграции на中型 сайте документации. Вот данные:
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— выявлена несовместимость двух пользовательских плагинов - Портирование пользовательских плагинов:
- Плагин подсветки кода → нативная поддержка Sätteri (
features: { syntaxHighlight: true }) - Кастомный callout → переписан через API
transformsот Sätteri (35 строк Rust → JS-привязка) - Кастомные якоря → отказ, переход на ручные ID
- Плагин подсветки кода → нативная поддержка Sätteri (
- Включение
cf(): добавлениеadvancedRouting: { cf: true }в конфигурацию адаптераcloudflare, удаление кода ручных биндингов
5.3 Результаты
| Показатель | До миграции | После миграции | Изменение |
|---|---|---|---|
| Время сборки | 87 с | 42 с | -52% |
| Стоимость CI (в месяц) | ~$45 | ~$22 | -51% |
| Код адаптера Cloudflare | 47 строк | 3 строки | -94% |
| Частота багов «локально vs прод» | ~2–3 в месяц | 0 (на момент публикации) | -100% |
6. Вывод: скорость против экосистемы
Astro 6.4 задаёт вопрос, с которым рано или поздно сталкивается каждый SSG-фреймворк: стоит ли нативная скорость жертвы совместимостью с плагинами?
Ответ Astro прагматичен — никакой спешки, но направление задано. Sätteri подключается опционально; unified помечен как устаревший, но пока не удалён. Это переходное окно даёт экосистеме время на адаптацию.
Три вещи, которые можно вынести прямо сейчас:
- Markdown-пайплайн теперь подключаемый — это значит, что в будущем могут появиться процессоры на Python, Go или браузерные нативные процессоры
- Проекты с насыщенным контентом могут переключиться на Sätteri уже сейчас, сократив время сборки вдвое
- Пользователям Cloudflare практически нет причин не использовать
cf()— он сжимает шесть строк биндингов в одну, без побочных эффектов
И ещё один неочевидный сигнал: название Sätteri происходит от шведского «упорядочивать/сортировать». Команда Astro выбрала не громкий маркетинговый термин о производительности, а ремесленное слово. Это не совпадение.
Ссылки
- Релизный блог Astro 6.4
- RFC: нативный Markdown / MDX
- npm-пакет
@astrojs/markdown-sätteri - Документация адаптера
@astrojs/cloudflare - Пример интеграции Hono + Astro