Astro.js: современный фреймворк для контент-ориентированных сайтов
Что такое Astro и зачем он нужен
Astro — это фреймворк для сайтов и веб-приложений, который по умолчанию рендерит большую часть страницы в статический HTML на сервере, а JavaScript подключает только там, где он действительно нужен. Такой подход называется islands architecture: основная часть страницы остается «легкой», а интерактивность добавляется небольшими изолированными фрагментами.
Astro поддерживает несколько UI-фреймворков одновременно: React, Preact, Vue, Svelte, Solid и веб-компоненты. Это делает его удобным, когда нужно сочетать контентные страницы, маркетинговые лендинги, документацию и отдельные интерактивные блоки в одном проекте.
Ключевые преимущества
Производительность «из коробки»
Astro проектировался как server-first решение, поэтому большинство страниц может отдаваться без необходимости тащить на клиент целый фреймворк runtime. В документации прямо отмечено, что это снижает сложность по сравнению с другими UI-фреймворками, потому что на сервере нет необходимости управлять реактивностью, хуками, stale closures и похожими механизмами.
UI-агностичность
Astro имеет систему интеграций и renderer-ов, через которую подключаются UI-фреймворки, адаптеры для серверного рендеринга и дополнительные возможности вроде MDX или sitemap. Для обычного проекта подключение официальной интеграции делается в несколько строк.
- React — для компонентов с богатым состоянием,
- Preact — там, где нужен React-подобный синтаксис с минимальным весом,
- Svelte — для компактных и производительных виджетов,
- Vue — если команда привыкла к его экосистеме,
- Lit — для веб-компонентов.
На практике это особенно ценно при миграции старых проектов: вы можете постепенно переписывать компоненты, не ломая всё сразу.
Гибкая модель интерактивности
Astro использует компонентный подход, но интерактивность включается точечно через hydration directives: client:load, client:idle, client:visible, client:media и client:only. Это позволяет запускать JavaScript только для конкретных элементов интерфейса и выбирать момент загрузки в зависимости от приоритета UI.
Content Collections
Начиная с версии 2.0, Astro предлагает типизированные коллекции контента — встроенную систему для работы с Markdown, MDX и другими форматами с полной поддержкой TypeScript.
Технические основы: как это работает
Этапы сборки
Когда вы запускаете astro build, происходит следующее:
- Парсинг компонентов — Astro читает все
.astro-файлы и компоненты UI-фреймворков.
- Статическая генерация — выполняется JavaScript в
frontmatter (часть между ---), которая может делать запросы к API, читать файлы и т.д.
- Рендеринг в HTML — вся разметка превращается в чистый HTML.
- Выделение «островов» — компоненты с клиентскими директивами (
client:load, client:idle и т.д.) упаковываются отдельно.
- Гидратация — при загрузке страницы браузер загружает только нужные острова, причём строго по заданной стратегии.
Клиентские директивы
Директивы определяют, когда браузер должен оживить компонент:
| Директива |
Поведение |
client:load |
Немедленно при загрузке страницы |
client:idle |
Когда браузер освободился (через requestIdleCallback) |
client:visible |
Когда компонент попадает в область видимости (Intersection Observer) |
client:media="(max-width: 768px)" |
При совпадении медиазапроса |
client:only="preact" |
Только на клиенте, без серверного рендеринга |
Синтаксис .astro-файлов
.astro-файл делится на две части: frontmatter (серверный JavaScript) и шаблон (HTML-подобная разметка).
---
// Всё здесь выполняется ТОЛЬКО на сервере/в момент сборки.
// Этот код никогда не попадёт в браузер.
import Header from '../components/Header.astro';
import ProductCard from '../components/ProductCard.jsx'; // React-компонент
import { getCollection } from 'astro:content';
const products = await fetch('https://api.example.com/products')
.then(res => res.json());
const { title = 'Каталог товаров' } = Astro.props;
---
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>{title}</title>
</head>
<body>
<!-- Статический Astro-компонент -->
<Header />
<main>
<h1>{title}</h1>
<ul class="product-grid">
{products.map(product => (
<!-- ProductCard — React-компонент -->
<ProductCard client:visible name={product.name}/>
))}
</ul>
</main>
</body>
</html>
<style>...</style>
Astro автоматически изолирует CSS-стили — каждый .astro-файл получает уникальный хэш-атрибут.
Layouts — переиспользуемые макеты
В Astro макеты — это просто компоненты со слотом <slot />:
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'Мой Astro-сайт' } = Astro.props;
---
<!doctype html>
<html lang="ru">
<head>
...
</head>
<body>
<nav class="site-nav"> ... </nav>
<!-- Содержимое страницы подставляется сюда -->
<slot />
<footer>© 2024 Мой сайт</footer>
</body>
</html>
Маршрутизация и работа с данными
Файловая маршрутизация
Astro использует файловую систему для маршрутизации — принцип, знакомый по Next.js:
src/pages/
├── index.astro → /
├── about.astro → /about
├── blog/
│ ├── index.astro → /blog
│ └── [slug].astro → /blog/любой-slug
└── api/
└── products.ts → /api/products (API-эндпоинт)
Динамические маршруты и getStaticPaths
Для генерации динамических страниц используется функция getStaticPaths:
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';
// Эта функция говорит Astro: "вот все возможные URL, которые нужно сгенерировать"
export async function getStaticPaths() {
const posts = await getCollection('blog'); // читаем коллекцию из src/content/blog/
return posts.map(post => ({
params: { slug: post.slug },
props: { post }, // передаём данные в компонент
}));
}
const { post } = Astro.props;
const { Content } = await post.render(); // превращаем Markdown в компонент
---
<BlogLayout title={post.data.title} description={post.data.description}>
<article>
<h1>{post.data.title}</h1>
<time>{post.data.publishedAt.toLocaleDateString('ru-RU')}</time>
<!-- Content — это готовый к рендерингу HTML из Markdown-файла -->
<Content />
</article>
</BlogLayout>
Content Collections с типизацией
Система коллекций позволяет описать схему frontmatter и получить полную автодополнение в редакторе:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
type: 'content', // 'content' для Markdown, 'data' для JSON/YAML
schema: z.object({
title: z.string(),
description: z.string(),
publishedAt: z.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
// Если поля нет — TypeScript немедленно об этом сообщит
coverImage: z.string().optional(),
}),
});
export const collections = {
blog: blogCollection,
};
После этого getCollection('blog') вернёт типизированный массив, и вы не сможете случайно обратиться к несуществующему полю.
API-эндпоинты
Astro позволяет создавать серверные API прямо внутри проекта:
// src/pages/api/newsletter.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const { email } = body;
if (!email || !email.includes('@')) {
return new Response(
JSON.stringify({ error: 'Некорректный email' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// Здесь можно вызвать любой сервис: Mailchimp, Resend, собственную БД...
await subscribeToNewsletter(email);
return new Response(
JSON.stringify({ success: true }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
};
Сравнение с другими фреймворками
Понять место Astro в экосистеме проще всего через сравнение с конкурентами. Но важно понимать: это не «Astro против Next.js», а «разные инструменты для разных задач».
Сводная таблица
| Критерий |
Astro |
Next.js |
SvelteKit |
Nuxt |
| JS по умолчанию |
✅ Нет |
❌ Да |
⚠️ Минимум |
❌ Да |
| Смешение фреймворков |
✅ Да |
❌ Нет |
❌ Нет |
❌ Нет |
| SSR |
✅ Да |
✅ Да |
✅ Да |
✅ Да |
| Встроенный роутер |
✅ Файловый |
✅ Файловый |
✅ Файловый |
✅ Файловый |
| Лучший случай |
Контент-сайты |
Rich SPA |
Svelte-приложения |
Vue-приложения |
| Порог входа |
Низкий |
Средний |
Средний |
Средний |
Интеграция с UI-фреймворками на практике: Preact
Preact — это компактная библиотека компонентов, совместимая с React-экосистемой через preact/compat. В официальных материалах Preact подчеркивается, что compatibility layer позволяет использовать React-компоненты как drop-in replacement в ряде случаев, а сам Preact отличается более близким к DOM поведением и меньшим runtime-overhead, в том числе без synthetic event system.
В Astro интеграция с Preact подключается официальным пакетом @astrojs/preact. Для обычного использования достаточно команды astro add preact; после этого Astro умеет рендерить Preact-компоненты и гидрировать их через client:* директивы.
Установка и конфигурация
# Добавляем интеграцию одной командой
npx astro add preact
# Или вручную через npm
npm install @astrojs/preact preact
После этого обновляем конфиг:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
export default defineConfig({
integrations: [
preact({
// compat: true — включает слой совместимости с React,
// что позволяет использовать некоторые React-библиотеки с Preact
compat: true,
}),
],
});
Особенности и тонкости при работе с Preact в Astro
Атрибуты class vs className. В Astro-компонентах (.astro) всегда используется class. В Preact-компонентах (.jsx) тоже рекомендуется class, а не className — это одно из отличий Preact от React.
Серверный рендеринг Preact-компонентов. Даже без клиентской директивы Preact-компоненты рендерятся на сервере в HTML. Директива лишь добавляет гидратацию. Это значит, что поисковик увидит контент компонента, даже если JavaScript в браузере отключён.
client:only="preact" нужен в редких случаях — когда компонент использует браузерные API (localStorage, window) и не может быть отрендерен на сервере. В таком случае Astro пропускает серверный рендеринг полностью.
// Компонент, который читает из localStorage — не может работать на сервере
export default function ThemeToggle() {
const [theme, setTheme] = useState(
// localStorage доступен только в браузере
() => localStorage.getItem('theme') ?? 'light'
);
// ...
}
<!-- Правильно: client:only пропускает SSR -->
<ThemeToggle client:only="preact" />
Итог
Astro полезен там, где важны скорость первой отрисовки, небольшой клиентский бандл и гибкая интеграция с несколькими UI-фреймворками. Preact в этой экосистеме хорошо подходит для компактных интерактивных компонентов и для проектов, где нужна совместимость с частью React-экосистемы, но без полного веса React runtime.