Как создать блог на Astro: установка, MDX, Content Collections
Пошаговая настройка блога на Astro: структура проекта, маршруты, MDX с компонентами, Content Collections со схемой Zod. Готовый фундамент для контентного сайта.
Требования
- Node.js 18+ (LTS)
- npm, pnpm или yarn
- Базовые знания HTML/CSS/JS
Как создать блог на Astro: полная инструкция
Проблема: нужно быстро поднять быстрый блог или контентный сайт с хорошим SEO, но без сложностей Next.js и без перегруженного JavaScript.
Решение: Astro — статический генератор (SSG), который рендерит HTML по умолчанию и добавляет интерактивность точечно (islands architecture). В итоге получается лёгкая страница, понятная поисковикам и пользователям.
В этой статье — пошаговая настройка блога на Astro: установка, структура проекта, маршруты, MDX (Markdown с компонентами) и Content Collections со схемой на Zod для валидации контента.
В чём проблема: выбор фреймворка для контентного сайта
Если нужен блог, документация или маркетинговый сайт, есть три основных варианта:
| Критерий | Astro | Next.js | Gatsby |
|---|---|---|---|
| Модель по умолчанию | SSG, HTML-first | SSR/SSG, JS-heavy | SSG (React) |
| JS в браузере | Минимум (islands) | Много (гидрация всего) | Много (React-дерево) |
| Контент из коробки | MDX, Markdown, Collections | Нужны решения | GraphQL + плагины |
| Скорость контент-сайта | Очень высокая | Зависит от настроек | Хорошая после билда |
| Кривая обучения | Низкая (HTML + островки) | Выше (React, App Router) | Выше (GraphQL, плагины) |
| Лучше всего под | Блог, доки, лендинг, портфолио | SPA, приложения, сложный роутинг | Контент на React + CMS |
Вывод: для блога, документации или маркетингового сайта Astro часто даёт самый лёгкий результат при минимальном JavaScript.
Рабочее решение: пошаговая настройка
Шаг 1: Создание проекта Astro
# Официальный способ: интерактивный мастер
npm create astro@latest
# или
pnpm create astro@latest
Мастер спросит:
- Шаблон (выбери
blogдля блога илиminimalдля чистого старта) - TypeScript (рекомендуется включить — пригодится для Content Collections)
- ESLint и форматирование (по желанию)
Шаг 2: Проверка системных требований
# Проверить Node.js (должно быть >= 18)
node -v
# Проверить менеджер пакетов
npm -v # или pnpm -v / yarn -v
Требования:
- Node.js 18+ (рекомендуется LTS: 20 или 22)
- npm, pnpm или yarn
- Редактор кода (VS Code + плагин Astro)
Шаг 3: Запуск dev-сервера
# Перейти в папку проекта
cd <имя-проекта>
# Запустить dev-сервер
npm run dev
# или
pnpm dev
Открой http://localhost:4321 — должен открыться стартовый сайт.
Шаг 4: Проверка сборки перед деплоем
# Собрать проект в папку dist/
npm run build
# или
pnpm build
Если сборка прошла без ошибок — проект готов к деплою.
Структура проекта Astro
Типичная структура блога на Astro:
/
├─ astro.config.mjs # Конфигурация Astro
├─ package.json # Зависимости и скрипты
├─ src/
│ ├─ pages/ # Маршруты (file-based routing)
│ │ ├─ index.astro # Главная страница
│ │ ├─ about.astro # /about
│ │ └─ blog/
│ │ ├─ index.astro # /blog (список статей)
│ │ └─ [slug].astro # /blog/:slug (страница статьи)
│ ├─ layouts/ # Layouts (общая обвязка)
│ │ └─ BaseLayout.astro
│ ├─ components/ # UI компоненты
│ │ └─ Callout.astro
│ ├─ content/ # Контент (Content Collections)
│ │ └─ blog/ # Статьи блога
│ └─ styles/ # Глобальные стили
└─ public/ # Статика (копируется как есть)
└─ favicon.svg
Правило маршрутизации: файл в src/pages/ = маршрут.
src/pages/index.astro→/src/pages/about.astro→/aboutsrc/pages/blog/[slug].astro→/blog/:slug
Первый layout: BaseLayout.astro
Создай файл src/layouts/BaseLayout.astro:
---
// Пропсы приходят со страницы; задаём значения по умолчанию
const {
title = "Site",
description = "Astro site",
} = Astro.props;
---
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<header style="padding:16px; border-bottom:1px solid rgba(255,255,255,.1)">
<a href="/">Главная</a>
<span style="opacity:.6; margin-left:8px">|</span>
<a href="/blog" style="margin-left:8px">Блог</a>
</header>
<main style="max-width: 860px; margin: 0 auto; padding: 24px;">
<slot />
</main>
<footer style="padding:16px; border-top:1px solid rgba(255,255,255,.1); opacity:.7">
© {new Date().getFullYear()}
</footer>
</body>
</html>
<slot />— это “сюда вставится содержимое страницы”.
Первая страница: index.astro
Создай файл src/pages/index.astro:
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Главная" description="Стартовый проект на Astro">
<h1>Привет, Astro</h1>
<p>Если страница открылась — ты уже в деле.</p>
</BaseLayout>
Открой http://localhost:4321 — должна отобразиться главная страница.
Подключаем MDX: Markdown с компонентами
Установка MDX
MDX — это Markdown, который умеет встраивать компоненты и JS-выражения.
npx astro add mdx
# или
pnpm astro add mdx
После установки в astro.config.mjs появится интеграция @astrojs/mdx.
Первая MDX-страница
Создай файл src/pages/guide.mdx:
---
title: "Гайд по Astro"
description: "Пробная MDX-страница"
---
# MDX в Astro работает ✅
Это обычный Markdown… но с бонусами.
- списки
- `код`
- и компоненты ниже
Открой /guide — должна отрендериться страница.
Встраиваем компоненты в MDX
Создаём компонент Callout.astro
Создай файл src/components/Callout.astro:
---
const { type = "info", title = "Важно" } = Astro.props;
const styles = {
info: "background: rgba(0, 180, 255, .08); border: 1px solid rgba(0, 180, 255, .35);",
warn: "background: rgba(255, 180, 0, .08); border: 1px solid rgba(255, 180, 0, .35);",
danger: "background: rgba(255, 80, 80, .08); border: 1px solid rgba(255, 80, 80, .35);",
};
---
<section style={`padding: 14px 16px; border-radius: 12px; ${styles[type] ?? styles.info}`}>
<strong style="display:block; margin-bottom: 6px">{title}</strong>
<div><slot /></div>
</section>
Используем компонент в MDX
Обнови src/pages/guide.mdx:
---
title: "Гайд по Astro"
description: "Пробная MDX-страница"
---
import Callout from "../components/Callout.astro";
# MDX + компоненты
<Callout type="info" title="Смысл MDX">
Ты пишешь статью как Markdown, но можешь вставлять "живые" компоненты.
</Callout>
<Callout type="warn" title="Пара слов о порядке">
Не превращай статью в React-приложение. Это всё ещё контент.
</Callout>
Content Collections: контент с валидацией
Зачем нужны collections
Чтобы:
- Описать схему фронтматтера (какие поля обязательны)
- Ловить ошибки на этапе билда (а не в проде)
- Получать типизацию (особенно приятно в TS)
Настраиваем src/content.config.ts
Создай файл src/content.config.ts:
// src/content.config.ts
import { defineCollection } from "astro:content";
import { z } from "astro/zod";
const blog = defineCollection({
schema: z.object({
title: z.string().min(5),
description: z.string().min(20),
pubDate: z.date(),
updatedDate: z.date().optional(),
tags: z.array(z.string()).default([]),
category: z.string().default("Frontend"),
draft: z.boolean().default(false),
cover: z.string().optional(),
slug: z.string().min(3),
}),
});
export const collections = { blog };
astro/zod— это реэкспорт Zod для схем коллекций.
Первая статья в коллекции
Создай файл src/content/blog/astro-part-1.mdx:
---
title: "Astro: старт (часть 1)"
description: "Статья в content collection, чтобы потом красиво собрать список и страницы."
pubDate: 2026-01-05T16:00:00.000Z
tags:
- astro
- mdx
- frontend
category: Frontend
draft: false
slug: "astro-part-1"
---
import Callout from "../../components/Callout.astro";
# Astro: старт (часть 1)
<Callout type="info" title="Это пост из коллекции">
Он лежит в src/content/blog и валидируется схемой.
</Callout>
Здесь начинается содержание статьи…
Выводим список статей: /blog
Создай файл src/pages/blog/index.astro:
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
// Загружаем все посты, скрываем черновики, сортируем по дате
const posts = (await getCollection("blog"))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
<BaseLayout title="Блог" description="Список статей на Astro">
<h1>Блог</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.data.slug}/`}>{post.data.title}</a>
<div style="opacity:.7; font-size: 14px">{post.data.description}</div>
</li>
))}
</ul>
</BaseLayout>
Страница статьи: /blog/:slug
Создай файл src/pages/blog/[slug].astro:
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
// Для SSG Astro нужен список всех путей
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts
.filter(p => !p.data.draft)
.map((post) => ({
params: { slug: post.data.slug },
props: { post },
}));
}
const { post } = Astro.props;
// У коллекционных MDX/MD есть render() — получаем компонент контента
const { Content } = await post.render();
---
<BaseLayout title={post.data.title} description={post.data.description}>
<article>
<h1>{post.data.title}</h1>
<p style="opacity:.7; margin-top:-6px">
{post.data.pubDate.toLocaleDateString("ru-RU")}
</p>
<Content />
</article>
</BaseLayout>
Проверка результата
1. Проверка списка статей
# Запустить dev-сервер
npm run dev
# Открыть http://localhost:4321/blog
Должен отобразиться список статей из src/content/blog/.
2. Проверка страницы статьи
# Открыть http://localhost:4321/blog/astro-part-1/
Должна отобразиться страница статьи с содержимым MDX.
3. Проверка сборки
# Собрать проект
npm run build
# Проверить dist/
ls -la dist/
Если сборка прошла без ошибок — все маршруты и коллекции настроены верно.
Типичные ошибки
❌ Ошибка: getCollection("blog") — коллекция не найдена
Причина: Нет src/content.config.ts или папки src/content/blog/.
Решение:
# Создать конфиг
touch src/content.config.ts
# Создать папку для статей
mkdir -p src/content/blog
❌ Ошибка валидации frontmatter при билде
Причина: Поля не совпадают со схемой (дата, обязательные поля).
Решение: Проверить title, description, pubDate, slug в .mdx. Даты в формате ISO: 2026-01-05T16:00:00.000Z.
❌ MDX-страница не открывается / 404
Причина: Файл не в src/pages/ или опечатка в имени.
Решение: Роут = путь от pages: guide.mdx → /guide.
❌ Компонент в MDX не найден
Причина: Неверный путь импорта (относительно файла MDX).
Решение: Импорт от корня файла: ../../components/Callout.astro.
❌ post.render is not a function
Причина: Используется сырой объект поста вместо результата getCollection.
Решение: В [slug].astro брать post из getStaticPaths → props и вызывать post.render().
Где применять
- Блоги: контентные сайты с MDX и Collections
- Документация: техническая документация с компонентами
- Лендинги: маркетинговые страницы с быстрым SEO
- Портфолио: сайты-визитки с минимальным JS
Связанные статьи:
Документация:



Комментарии