DEV Community

Андрей Викулов (VProger)
Андрей Викулов (VProger)

Posted on • Originally published at viku-lov.ru on

Как создать блог на Astro: установка, MDX, Content Collections

Как создать блог на Astro: установка, MDX, Content Collections

Как создать блог на 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

Enter fullscreen mode Exit fullscreen mode

Мастер спросит:

  • Шаблон (выбери blog для блога или minimal для чистого старта)
  • TypeScript (рекомендуется включить — пригодится для Content Collections)
  • ESLint и форматирование (по желанию)

Шаг 2: Проверка системных требований


Проверить Node.js (должно быть >= 18)

node -v

Проверить менеджер пакетов

npm -v # или pnpm -v / yarn -v

Enter fullscreen mode Exit fullscreen mode

Требования:

  • Node.js 18+ (рекомендуется LTS: 20 или 22)
  • npm, pnpm или yarn
  • Редактор кода (VS Code + плагин Astro)

Шаг 3: Запуск dev-сервера


Перейти в папку проекта

cd <имя-проекта>

Запустить dev-сервер

npm run dev

или

pnpm dev

Enter fullscreen mode Exit fullscreen mode

Открой http://localhost:4321 — должен открыться стартовый сайт.

Шаг 4: Проверка сборки перед деплоем


Собрать проект в папку dist/

npm run build

или

pnpm build

Enter fullscreen mode Exit fullscreen mode

Если сборка прошла без ошибок — проект готов к деплою.


Структура проекта 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

Enter fullscreen mode Exit fullscreen mode

Правило маршрутизации: файл в src/pages/ = маршрут.

  • src/pages/index.astro → /
  • src/pages/about.astro → /about
  • src/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)">

[Главная](/)

<span style="opacity:.6; margin-left:8px">|</span>

[Блог](/blog)

</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>

Enter fullscreen mode Exit fullscreen mode

— это "сюда вставится содержимое страницы".


Первая страница: index.astro

Создай файл src/pages/index.astro:


---

import BaseLayout from "../layouts/BaseLayout.astro";

---

<BaseLayout title="Главная" description="Стартовый проект на Astro">

# Привет, Astro

Если страница открылась — ты уже в деле.

</BaseLayout>

Enter fullscreen mode Exit fullscreen mode

Открой http://localhost:4321 — должна отобразиться главная страница.


Подключаем MDX: Markdown с компонентами

Установка MDX

MDX — это Markdown, который умеет встраивать компоненты и JS-выражения.


npx astro add mdx

или

pnpm astro add mdx

Enter fullscreen mode Exit fullscreen mode

После установки в astro.config.mjs появится интеграция @astrojs/mdx.

Первая MDX-страница

Создай файл src/pages/guide.mdx:


---

title: "Гайд по Astro"

description: "Пробная MDX-страница"

---

MDX в Astro работает ✅

Это обычный Markdown… но с бонусами.

- списки
- код
- и компоненты ниже

Enter fullscreen mode Exit fullscreen mode

Открой /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>

Enter fullscreen mode Exit fullscreen mode

Используем компонент в 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>

Enter fullscreen mode Exit fullscreen mode

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 };

Enter fullscreen mode Exit fullscreen mode

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>

Здесь начинается содержание статьи…

Enter fullscreen mode Exit fullscreen mode

Выводим список статей: /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">

# Блог

{posts.map((post) => (

- [{post.data.title}]({/blog/${post.data.slug}/})

))}

</BaseLayout>

Enter fullscreen mode Exit fullscreen mode

Страница статьи: /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>

# {post.data.title}

{post.data.pubDate.toLocaleDateString("ru-RU")}

<Content />

</article>

</BaseLayout>

Enter fullscreen mode Exit fullscreen mode

Проверка результата

  1. Проверка списка статей

Запустить dev-сервер

npm run dev

Открыть http://localhost:4321/blog

Enter fullscreen mode Exit fullscreen mode

Должен отобразиться список статей из src/content/blog/.

  1. Проверка страницы статьи

Открыть http://localhost:4321/blog/astro-part-1/

Enter fullscreen mode Exit fullscreen mode

Должна отобразиться страница статьи с содержимым MDX.

  1. Проверка сборки

Собрать проект

npm run build

Проверить dist/

ls -la dist/

Enter fullscreen mode Exit fullscreen mode

Если сборка прошла без ошибок — все маршруты и коллекции настроены верно.


Типичные ошибки

❌ Ошибка: getCollection("blog") — коллекция не найдена

Причина: Нет src/content.config.ts или папки src/content/blog/.

Решение:


Создать конфиг

touch src/content.config.ts

Создать папку для статей

mkdir -p src/content/blog

Enter fullscreen mode Exit fullscreen mode

❌ Ошибка валидации 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

Связанные статьи:

  • SEO, sitemap и RSS в Astro
  • Actions, API Routes, SSR и деплой
  • TypeScript: основы и философия

Документация:


Read more on viku-lov.ru

Top comments (0)