needhelp
← Back to blog

Astro 6.4: разбор — подключаемый Markdown-пайплайн, Sätteri на Rust и революция в деплое на Cloudflare

by needhelp
Astro
фронтенд
Rust
Cloudflare
Markdown
SSG
веб-разработка

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Ускорение
Официальная документация Astro142 с63 с2,25×
Документация Cloudflare120 с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 станет процессором по умолчанию в одном из будущих мажорных релизов. Это означает, что у вас есть два варианта:

  1. Оценить и портировать сейчас — если плагинов мало, можно переключиться сразу
  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 поддерживает нативно

ВозможностьUnifiedSätteriПримечание
GFM (таблицы, списки задач и т.д.)✅ плагин✅ нативноБесплатно
Smartypants (типографские кавычки)✅ плагин✅ нативноБесплатно
Синтаксис directive⚠️ требуется remark-directive✅ нативно features: { directive: true }Проще
Плагины MDAST/HAST✅ всеКлючевое ограничение
Пользовательские компоненты✅ MDX✅ MDXSä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()); // ← одна строка — все биндинги 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-биндинг, выгода невелика. Но если в ходу 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: Обновление

Terminal window
npx @astrojs/upgrade
# или
bunx @astrojs/upgrade

Это автоматически обработает обновление версий и переустановку зависимостей. Если вы используете @astrojs/cloudflare, адаптер также будет обновлён.

Этап 2: Миграция конфигурации

Terminal window
# Проверка нового формата конфигурации
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 Шаги миграции

  1. Обновление CLI: npx @astrojs/upgrade — без ошибок
  2. Миграция конфигурации: перенос remarkPlugins / rehypePlugins в processor: unified({...})
  3. Оценка Sätteri: запуск npx astro check --processor sätteri — выявлена несовместимость двух пользовательских плагинов
  4. Портирование пользовательских плагинов:
    • Плагин подсветки кода → нативная поддержка Sätteri (features: { syntaxHighlight: true })
    • Кастомный callout → переписан через API transforms от Sätteri (35 строк Rust → JS-привязка)
    • Кастомные якоря → отказ, переход на ручные ID
  5. Включение cf(): добавление advancedRouting: { cf: true } в конфигурацию адаптера cloudflare, удаление кода ручных биндингов

5.3 Результаты

ПоказательДо миграцииПосле миграцииИзменение
Время сборки87 с42 с-52%
Стоимость CI (в месяц)~$45~$22-51%
Код адаптера Cloudflare47 строк3 строки-94%
Частота багов «локально vs прод»~2–3 в месяц0 (на момент публикации)-100%

6. Вывод: скорость против экосистемы

Astro 6.4 задаёт вопрос, с которым рано или поздно сталкивается каждый SSG-фреймворк: стоит ли нативная скорость жертвы совместимостью с плагинами?

Ответ Astro прагматичен — никакой спешки, но направление задано. Sätteri подключается опционально; unified помечен как устаревший, но пока не удалён. Это переходное окно даёт экосистеме время на адаптацию.

Три вещи, которые можно вынести прямо сейчас:

  1. Markdown-пайплайн теперь подключаемый — это значит, что в будущем могут появиться процессоры на Python, Go или браузерные нативные процессоры
  2. Проекты с насыщенным контентом могут переключиться на Sätteri уже сейчас, сократив время сборки вдвое
  3. Пользователям Cloudflare практически нет причин не использовать cf() — он сжимает шесть строк биндингов в одну, без побочных эффектов

И ещё один неочевидный сигнал: название Sätteri происходит от шведского «упорядочивать/сортировать». Команда Astro выбрала не громкий маркетинговый термин о производительности, а ремесленное слово. Это не совпадение.


Ссылки

Share this page