← Назад в блог

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

Пошаговая настройка блога на Astro: структура проекта, маршруты, MDX с компонентами, Content Collections со схемой Zod. Готовый фундамент для контентного сайта.

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

Требования

  • 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 для валидации контента.


В чём проблема: выбор фреймворка для контентного сайта

Если нужен блог, документация или маркетинговый сайт, есть три основных варианта:

КритерийAstroNext.jsGatsby
Модель по умолчаниюSSG, HTML-firstSSR/SSG, JS-heavySSG (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/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)">
      <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 из getStaticPathsprops и вызывать post.render().


Где применять

  • Блоги: контентные сайты с MDX и Collections
  • Документация: техническая документация с компонентами
  • Лендинги: маркетинговые страницы с быстрым SEO
  • Портфолио: сайты-визитки с минимальным JS

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

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


0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!