javascript

Релиз Astro 7: переход на Rust, улучшенное кэширование и поддержка AI-разработки

  • четверг, 25 июня 2026 г. в 00:00:10
https://habr.com/ru/companies/first/articles/1051258/

Astro — фреймворк для сайтов, который минимизирует поставку JavaScript на клиент, обеспечивая высокую производительность. 22 июня вышла седьмая версия, в которой разработчики серьёзно прокачали скорость. Компилятор .astro переписали на Rust, туда же перенесли обработку Markdown и MDX, а движок рендеринга заменили на систему с очередями. Вкупе с Vite 8 и новым бандлером Rolldown сборки ускорились на 15–61% по внутренним бенчмаркам. А поскольку самая быстрая сборка — та, которую не нужно запускать вовсе, в Astro 7 также стабилизировали кэширование маршрутов и добавили экспериментальных CDN-провайдеров кэша для Netlify, Vercel и Cloudflare.

В Astro 7 добавили продвинутый роутинг: появляется точка входа src/fetch.ts, дающая полный контроль над конвейером обработки запросов в Astro. Для разработки с участием ИИ Astro теперь умеет определять coding agents, запускать dev-сервер в фоне и выводить структурированные JSON-логи, когда агентам нужен машиночитаемый ответ.

Как обновиться до Astro 7

Чтобы обновить существующий проект до Astro 7, рекомендуется использовать автоматический CLI-инструмент @astrojs/upgrade:

# Рекомендованное:

npx @astrojs/upgrade

# Ручная установка:

npm install astro@latest

Для нового проекта используйте команду:

npm create astro@latest

Подробности по миграции — в upgrade guide.

Vite 8: долгожданный Rolldown

Astro 7 перешёл на Vite 8. Это обновление стало одним из крупнейших релизов Vite за последние годы. Главное изменение: Vite теперь поставляется с Rolldown, бандлером на Rust, который заменяет и esbuild, и Rollup единым стеком. В бенчмарках Rolldown показывает производительность в 10–30 раз выше, чем Rollup, при этом поддерживает тот же API плагинов Rollup и Vite.

Для пользователей Astro это означает более быстрые сборки без изменений конфигурации в большинстве проектов. В Vite 8 есть слой совместимости, который автоматически преобразует существующие настройки esbuild и rollupOptions в эквиваленты для Rolldown. Если проект использует кастомные плагины Vite, большая их часть должна продолжить работать, потому что Rolldown поддерживает тот же plugin API, что и Rollup.

Производительность: переход на Rust и оптимизации рендеринга

Astro 7 — самая быстрая версия фреймворка на текущий момент. По мере роста экосистемы все больше команд проверяют, насколько далеко можно зайти со сайтами на Astro. После выхода прошлой версии и крупного внутреннего рефакторинга фокус сместился на масштабирование для более масштабных и сложных проектов.

Сборка устроена так:

  1. Страницы сайта, контент и клиентские компоненты собираются в JavaScript.

  2. Затем этот код запускается как небольшой сервер: для каждой prerendered-страницы создается запрос, а полученный HTML сохраняется.

Astro 7 ускоряет оба этапа, но основной выигрыш дает первый — бандлинг сайта. Самые заметные улучшения появились благодаря переносу части самых медленных участков сборки в нативный код на Rust. Этап генерации тоже стал быстрее за счет новой стратегии рендеринга, которая эффективнее работает с очередями задач.

Во внутренних тестах общее время сборки сократилось на 15–61%, а некоторые сайты стали собираться более чем  в 2 раза быстрее. Наибольшую выгоду получили проекты, где существенную долю времени занимают компиляция .astro и обработка Markdown, потому что именно эти части были перенесены на Rust.

Бенчмарки запускались на MacBook Pro с Apple M4 Pro и 48 ГБ памяти:

Компилятор на Rust

Команда Astro создала новый компилятор для формата компонентов .astro, теперь написанный на Rust. Это полная переработка предыдущего компилятора на Go. Он в основном обратно совместим, но есть несколько отличий:

  • Больше нет автоматической коррекции HTML. Компилятор на Go молча переписывал разметку в «валидный HTML»: переставлял элементы, автоматически закрывал теги и перемещал узлы, что нередко удивляло пользователей и приводило к трудноуловимым ошибкам. Новый компилятор обрабатывает разметку как есть.

  • Строгость в стиле JSX. Незакрытые теги вроде <div>Hello и незавершенные атрибуты вроде <div class="Hello > теперь вызывают ошибки вместо молчаливой коррекции. Это реальные дефекты шаблонов, которые старый компилятор просто игнорировал, пытаясь вести себя как браузер.

  • Обработка пробелов как в JSX. Пробелы между элементами теперь схлопываются по правилам JSX, как в React и других JSX-фреймворках. Например, перенос строки между inline-элементами больше не создает видимый пробел:

<!-- До: рендерилось как "Hello World" (с пробелом) -->

<!-- После: рендерится как "HelloWorld" (без пробела) -->

<span>Hello</span>

<span>World</span>

<!-- Чтобы сохранить пробел, нужно явное выражение: -->

<span>Hello</span>{' '}<span>World</span>

Переход на Rust позволил поставлять нативные бинарники для поддерживаемых платформ с WASM-фолбэком для сред, где это требуется. Такой подход уже стал стандартом в мире JavaScript-инструментов и используется, например, в Rolldown и Lightning CSS. Внутри новый компилятор опирается на oxc для парсинга и Lightning CSS для CSS scoping.

Изолированно компилятор на Rust дал примерно 6% ускорения сборки для https://docs.astro.build. Причина в том, что компиляция .astro редко бывает основным узким местом: обычно больше времени съедают Markdown и бандлинг. Но на больших сайтах с тысячами страниц даже такой прирост важен, а его эффект складывается с остальными оптимизациями релиза.

Markdown и MDX на Rust

Astro 7 заменяет стандартный пайплайн Markdown и MDX на Sätteri — процессор на Rust, созданный jодной из core-команды Astro. Перевод сборок документации Astro и Cloudflare на Sätteri сократил время их сборки более чем на минуту, поэтому проекты с большим количеством Markdown-контента получили максимальный выигрыш в Astro 7.

До этого Markdown-пайплайн Astro работал на unified (remark, rehype и длинной цепочке JavaScript-зависимостей). На крупных сайтах с тысячами страниц именно этот этап часто был самым медленным: каждый файл проходил парсинг в JavaScript, затем обработку плагин за плагином по полному AST, а потом сериализацию обратно в HTML. В Astro 6.4 пайплайн сделали расширяемым и добавили Sätteri как опциональную альтернативу. В Astro 7 он становится стандартным вариантом.

В основе Sätteri лежат pulldown-cmark для CommonMark и Oxc для разбора выражений MDX — оба компонента написаны на Rust. Поставляются платформенные бинарники плюс WASM-фолбэк, по той же схеме, что и у нового .astro-компилятора. Но дело не только в скорости. Sätteri также нативно реализует множество Markdown-возможностей, которые раньше требовали отдельных плагинов:

  • GFM (таблицы, сноски, зачеркивание, task lists): в unified нужен remark-gfm.

  • Smart punctuation (типографские кавычки, длинные тире): в unified нужен remark-smartypants.

  • Heading IDs: в unified нужен remark-heading-id или аналог.

  • Container directives: в unified нужен remark-directive.

  • Math: в unified нужен remark-math.

  • Frontmatter (YAML, TOML): в unified нужен remark-frontmatter.

  • Superscript / subscript: в unified нужен remark-supersub или аналог.

  • Wikilinks: в unified нужен remark-wiki-link.

Функции, не включенные по умолчанию, активируются через features:

// astro.config.mjs

import { defineConfig } from 'astro/config';

import { satteri } from '@astrojs/markdown-satteri';

export default defineConfig({

  markdown: {

    processor: satteri({

      features: {

        directive: true,

        math: true,

        headingAttributes: true,

      },

    }),

  },

});

У Sätteri есть и собственный API плагинов. Плагины объявляют, какие типы узлов им интересны, и пропускают все остальное, вместо того чтобы каждый раз обходить все дерево целиком. Поэтому подключение плагинов здесь обходится заметно дешевле, чем в unified, где каждый плагин проходит по каждому узлу.

Если проект зависит от плагинов remark или rehype, пайплайн на базе unified по-прежнему доступен через @astrojs/markdown-remark:

// astro.config.mjs

import { defineConfig } from 'astro/config';

import { unified } from '@astrojs/markdown-remark';

import remarkToc from 'remark-toc';

export default defineConfig({

  markdown: {

    processor: unified({

      remarkPlugins: [remarkToc],

    }),

  },

});


Подробнее о возможностях Sätteri и его plugin API — на satteri.bruits.org.

Рендеринг через очередь вместо рекурсии

Queued rendering появился в прошлой версии как экспериментальная возможность, а теперь стал стабильным и используется по умолчанию. По данным команды, этот механизм дает ускорение примерно до 2,4 раза.

// astro.config.mjs

import { defineConfig } from "astro/config";

export default defineConfig({
  experimental: {
    queuedRendering: {
      enabled: true,
      pooling: true,
      contentCache: 1000
    }
  }
})

Раньше Astro рендерил страницы рекурсивно, когда дочерние узлы обрабатывались тем же render*-вызовом:

// render.ts

export function renderComponentToString(node: unknown): string {

  let destination = "";

  destination += <${node.name}>; // открытие тега

  // render attributes
  for (const child of node.children) {

    // Рекурсивный вызов для дочерних узлов
    destination += renderComponentToString(child);

  }

  destination += </${node.name}>; // closing tag

  return destination;
}


Новый движок использует очередь (или стек) и один цикл. Очередь заполняется дочерними узлами в правильном порядке, а цикл продолжает рендеринг, пока очередь не опустеет:

// render.ts

export function renderComponentToString(root: unknown): string {

  let destination = "";

  destination += <${root.name}>; // открытие тега

  // очередь, которая пополняется и опустошается в процессе рендеринга

  let stack = [root];

  while (stack.length > 0) {

    const node = stack.pop();

    if (Array.isArray(node)) {

      // Узлы в конце должны попасть в destination первыми

      for (let i = node.length - 1; i >= 0; i--) stack.push(node[i]);

      continue;

    }

    const nodeType = typeof node;

    if (nodeType === 'string') {

      destination += escapeHTML(node as string);

    }

  }

  destination += </${root.name}>; // closing tag

  return destination;

}


Первая реализация этого подхода состояла из двух фаз: сначала создавался упорядоченный список компонентов, затем список проходился и рендерился. Финальная реализация больше не создает полный список заранее: элементы рендерятся прямо во время обхода. Такой вариант быстрее первой итерации и требует меньше памяти, чем рекурсивный подход.

Продвинутый роутинг

Astro начинался как генератор статических сайтов с файловой маршрутизацией. Со временем middleware, redirects, rewrites, Actions, sessions и i18n заметно расширили серверные возможности Astro-приложений, но при этом усложнили контроль над жизненным циклом запроса. Если, например, нужно было запускать auth раньше Actions, логировать только рендеринг страниц или сначала отдавать часть запросов не-Astro API, приходилось обходить существующий пайплайн, а не собирать его напрямую из нужных частей.

В Astro 7 можно полностью перехватить управление конвейером обработки запросов, добавив в проект файл src/fetch.ts. Этот файл экспортирует стандартный паттерн fetch-обработчика, знакомый по Cloudflare Workers, Deno и Bun.

// src/fetch.ts

import { astro, FetchState } from 'astro/fetch';

export default {

  fetch(request: Request) {

    const state = new FetchState(request);

    // Проксирование API-запросов в backend-сервис

    if (state.url.pathname.startsWith('/api')) {

      const url = new URL(state.url.pathname + state.url.search, 'https://backend-api.example.com');

      return fetch(new Request(url, request));

    }

    // Фолбэк на страницы и endpoints Astro

    return astro(state);

  }

}


API совместим и с Hono, поэтому middleware Hono можно напрямую использовать внутри Astro-приложения:

// src/fetch.ts

import { astro } from 'astro/hono';

import { Hono } from 'hono';

import { basicAuth } from 'hono/basic-auth';

const app = new Hono();

app.use(basicAuth({ username: 'admin', password: 'secret' }));

app.use(astro());

export default app;

Для продвинутых сценариев отдельные возможности Astro можно собирать как независимые middleware, полностью контролируя порядок обработки запроса:

// src/fetch.ts

import { Hono } from 'hono';

import { actions, middleware, pages, i18n } from 'astro/hono';

import { auth } from './middleware/auth';

import { timing } from './middleware/timing';

const app = new Hono();

app.use(i18n());

app.use(auth());       // auth выполняется до actions

app.use(actions());

app.use(middleware());

app.use(timing());     // timing оборачивает только рендеринг страниц

app.use(pages());

export default app;

Если файл src/fetch.ts не добавлен, Astro работает точно так же, как и раньше.

Кэширование маршрутов: единый API для всех хостингов

Кэшировать on-demand-рендеринг сложнее, чем хотелось бы: у каждого хостинга свои правила, а единый стандарт управления из кода приложения долгое время отсутствовал. В Astro 7 для этого появляется route caching. Впервые функция появилась как эксперимент в прошлой версии, а теперь стабилизирована и дает единую платформенно-независимую модель: директивы задаются в маршрутах, а Astro сам делает все необходимое на выбранной платформе.

Провайдер кэша настраивается один раз, после чего можно использовать Astro.cache в страницах или context.cache в API-маршрутах и middleware, управляя кэшированием каждого ответа в терминах стандартной HTTP-семантики. В Astro есть встроенный провайдер memoryCache() для быстрого старта:

// astro.config.mjs

import { defineConfig, memoryCache } from 'astro/config';

export default defineConfig({

  cache: {

    provider: memoryCache(),

  },

});


---

Astro.cache.set({

  maxAge: 120, // кэш на 2 минуты

  swr: 60, // еще 1 минуту можно отдавать устаревшее значение во время ревалидации

  tags: ['products'], // тег для точечной инвалидции

});

---

Можно также декларативно задавать правила кэширования для групп маршрутов через routeRules, не добавляя логику кэша в код самих страниц:

// astro.config.mjs

export default defineConfig({

  cache: { provider: memoryCache() },

  routeRules: {

    '/blog/[...path]': { maxAge: 300, swr: 60 },

  },
});


Особенно полезно это в связке с live content collections. Live loader может вернуть вместе с данными cache hint — с тегами для инвалидации и временем последнего изменения. Если передать такой entry напрямую в Astro.cache.set(), Astro сам прочитает эти подсказки:

import { getLiveEntry } from 'astro:content';

const { entry } = await getLiveEntry('products', Astro.params.id);

// Astro прочитает cache hint из entry

Astro.cache.set(entry);

Кэшированные ответы можно очищать по требованию через cache.invalidate() — по тегу или по пути. Например, можно поднять webhook endpoint, который CMS будет дергать при изменении контента. Тогда провайдер выполнит точечную очистку без полного rebuild:

// src/pages/api/revalidate.ts

import type { APIRoute } from 'astro';

export const POST: APIRoute = async ({ request, cache }) => {

  // В реальном проекте здесь нужно проверить запрос и секретный токен.

  const { slug } = await request.json();

  // Очистить все ответы с тегом 'products'...

  await cache.invalidate({ tags: ["products"] });

  // ...очистить страницы, использовавшие конкретную запись...

  await cache.invalidate({ tags: [products:${slug}] });

  // ...или удалить конкретный путь.

  await cache.invalidate({ path: /products/${slug} });

  return new Response('Revalidated');

};


Если route caching уже использовался в экспериментальном режиме, единственное изменение — cache и routeRules теперь нужно вынести из блока experimental на верхний уровень конфигурации. В остальном API не изменился.

CDN-провайдеры кэша

Когда route caching появился в прошлой версии, для Node adapter был доступен только один in-memory-провайдер. В Astro 7 добавлены экспериментальные CDN-провайдеры для Netlify, Vercel и Cloudflare (последний — в закрытой бете).

Вместо хранения ответов в памяти эти провайдеры передают директивы кэширования на edge-сеть хостинга, что дает еще более быстрые ответы. При cache hit CDN может вернуть ответ напрямую, вообще не вызывая серверную функцию.

В одном из следующих релизов эти провайдеры планируется включать автоматически, а пока в экспериментальной фазе их нужно подключать вручную. Достаточно импортировать провайдер для используемого адаптера и задать его как cache.provider:

// astro.config.mjs

import { defineConfig } from 'astro/config';

import netlify from '@astrojs/netlify';

import { cacheNetlify } from '@astrojs/netlify/cache';

export default defineConfig({

  adapter: netlify(),

  cache: {

    provider: cacheNetlify(),

  },
});

Каждый адаптер экспортирует провайдер из своего entrypoint /cache:

  • Netlify: @astrojs/netlify/cache → cacheNetlify()

  • Vercel: @astrojs/vercel/cache → cacheVercel()

  • Cloudflare: @astrojs/cloudflare/cache → cacheCloudflare()

Один и тот же API Astro.cache, routeRulesи cache.invalidate() работает со всеми провайдерами. Каждый из них переводит директивы в нативные cache-control-заголовки платформы и механизмы очистки по тегам или путям.

Важно: провайдер Cloudflare требует функции Cloudflare Workers Cache, которая сейчас находится в закрытой бете. Без доступа к бете этот провайдер не работает и использовать его не следует.

Поддержка агентной разработки 

ИИ-агенты для написания кода  уже стали частью рабочего процесса многих разработчиков, и dev-сервер им нужен в другом виде, чем людям. Astro 7 — первый шаг к тому, чтобы Astro лучше подходил для для разработки под управлением агентов.

Фоновый dev-сервер

ИИ-агентам сложно работать с долгоживущими процессами. Обычно агент запускает команду, ждет завершения и читает вывод, но dev-сервер не завершается сам по себе. В итоге агенты зависают, поднимают дублирующиеся серверы, теряют информацию об уже запущенных экземплярах или оставляют зомби-процессы. По наблюдениям команды Astro, это видно и в автоматизированном triage issue: иногда агент тратит больше времени на возню с dev-сервером, чем на реальную проверку кода.

В Astro 7 появляется astro dev --background, который запускает dev-сервер как управляемый фоновый процесс. Astro также умеет определять, что работает внутри AI-агента, и автоматически включает background mode — без дополнительных флагов в агентных сценариях. Если агент не обнаружен, astro dev ведет себя как раньше.

$ astro dev --background

Dev server running at http://localhost:4321 (pid 12345)

  Stop:   astro dev stop

  Status: astro dev status

  Logs:   astro dev logs


Команда блокируется только до момента готовности сервера принимать запросы, после чего сообщает URL и PID и отсоединяется. Не нужны polling, sleep и парсинг терминального вывода в поисках строки Local:.

Lockfile защищает от запуска дубликатов. Если агент попытается поднять второй сервер, Astro просто вернет данные уже работающего экземпляра:

$ astro dev --background

Dev server already running at http://localhost:4321 (pid 12345)

Проверять статус и останавливать сервер можно из других shell-сессий:

$ astro dev status

Dev server running at http://localhost:4321 (pid 12345, uptime 123s, background)

$ astro dev stop

Stopped dev server (pid 12345).


Логи фонового сервера читаются через astro dev logs.

Все команды сделаны идемпотентными и «мягкими». Остановка несуществующего сервера проходит без ошибки, а повторный запуск возвращает уже существующий экземпляр. Это важно, потому что агенты часто теряют из виду текущее состояние процессов.

Кроме того, все работающие dev-серверы теперь публикуют health endpoint /_astro/status, который агент может опрашивать, чтобы убедиться, что сервер жив и готов принимать запросы.

JSON-логирование

Логгер Astro теперь полностью настраиваемый. Для агентов JSON-логирование включается автоматически, когда детектор агента активирует background mode. Для остальных пользователей эта возможность доступна через CLI или конфигурацию:

astro dev --json

// astro.config.mjs

import { defineConfig, logHandlers } from "astro/config";

export default defineConfig({

  logger: logHandlers.json()

})


JSON-логирование было самым популярным запросом в roadmap Astro, и не только из-за AI. Командам, которые разворачивают Astro SSR в production, нужны структурированные логи для интеграции с системами агрегации вроде Kibana, CloudWatch и Grafana/Loki. Прежний формат логов Astro был жестко ориентирован на человека: цвета, псевдографика, многострочные ошибки. Для машин такой вывод неудобен.

Новый API логгера также поддерживает кастомные обработчики и compose() для объединения нескольких логгеров. Например, можно оставить человекочитаемый вывод в консоли и параллельно писать JSON для инструментов, которым нужна структурированная телеметрия:

// astro.config.mjs

import { defineConfig, logHandlers } from "astro/config";

export default defineConfig({

  logger: logHandlers.compose(

    logHandlers.console(),

    logHandlers.json()

  )

})

Подробнее о поддержке AI-инструментов — в AI guide.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ нового VDS — HABRFIRSTVDS.

Положение об акции