Astro в 2025–2026: быстрый старт для фронтенд-разработчика (часть 1) — структура проекта, маршруты, MDX и Content Collections
Большое и понятное введение в Astro (2025–2026): что это за фреймворк, почему он быстрый и SEO-дружелюбный, как устроен проект, как работают страницы и маршруты, как подключить MDX, и как завести Content Collections с валидацией схем (Zod).
Astro в 2025–2026: быстрый старт (часть 1)
Если коротко: Astro — это фреймворк для контентных сайтов, где важны скорость, SEO и контроль над тем, сколько JS реально уезжает в браузер. Блоги, документация, маркетинг, каталоги, лендинги, портфолио — его родная среда.
А если не коротко — сейчас будет.
Цель части 1: чтобы ты (или джун) смог:
- поднять проект на Astro
- понять структуру
src/pages, компоненты, layout’ы- сделать маршруты (включая динамические)
- подключить MDX и начать писать страницы/статьи
- завести Content Collections со схемой (валидация + типы)
1) Почему Astro вообще стоит трогать в 2025–2026
Astro “по умолчанию быстрый”
Философия простая: рендерим HTML, а интерактивность добавляем точечно (islands). Это почти всегда быстрее, чем “SPA везде и всегда”.
Astro “по умолчанию дружит с SEO”
Потому что результатом часто является обычная страница (HTML), понятная роботу, браузеру и человеку.
Astro 5+ сделал контент ещё удобнее
В Astro 5 появился Content Layer — более гибкая и типобезопасная система контента и загрузчиков (loaders), которая позволяет подтягивать данные не только из markdown-файлов, но и из API/ CMS/ любых источников — и всё это складывать в единый слой данных.
Плюс Astro 5 упростил “режимы рендера”: статик по умолчанию, а SSR — точечно, где надо (через адаптер + prerender = false).
2) Установка и создание проекта Astro
Требования
- Node.js LTS (обычно достаточно “последнего LTS”)
- npm / pnpm / yarn (я покажу примеры на npm и pnpm)
Создать проект
npm create astro@latest
# или
pnpm create astro@latest
Официально именно так предлагают стартовать проект. ([Astro][1])
Дальше мастер спросит:
- шаблон (например,
blog,minimal,starter) - TypeScript (можно включить сразу)
- eslint/format и т.д.
Запуск dev-сервера
npm run dev
# или
pnpm dev
3) Структура проекта Astro (чтобы не блуждать как в лабиринте)
Типичная база (упрощённо):
/
├─ astro.config.mjs
├─ package.json
├─ src/
│ ├─ pages/
│ ├─ layouts/
│ ├─ components/
│ ├─ content/ (если используешь collections)
│ └─ styles/
└─ public/
src/pages/ — файловая маршрутизация (file-based routing)
src/pages/index.astro→/src/pages/about.astro→/aboutsrc/pages/blog/index.astro→/blog
Правило простое: файл = маршрут.
src/components/ — компоненты UI
Кнопки, карточки, шапки, футеры, “вставки в статью”.
src/layouts/ — лэйауты (общая обвязка страниц)
Обычно: базовый layout с <head>, шапкой/футером, контейнером, слотами.
public/ — статика “как есть”
Файлы отсюда доступны напрямую:
public/favicon.svg→/favicon.svgpublic/assets/...→/assets/...
4) Первая страница на Astro: layout + slot
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— это “сюда вставится содержимое страницы”.
src/pages/index.astro
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Главная" description="Стартовый проект на Astro">
<h1>Привет, Astro</h1>
<p>Если страница открылась — ты уже в деле.</p>
</BaseLayout>
5) Маршруты: статические и динамические
Статический маршрут
src/pages/about.astro → /about — без сюрпризов.
Динамический маршрут (slug)
src/pages/blog/[slug].astro → /blog/что-угодно
База выглядит так (пока без контента, просто демонстрация):
---
const { slug } = Astro.params;
---
<h1>Пост: {slug}</h1>
Дальше мы подключим Content Collections и начнём получать реальные статьи по
slug.
6) Подключаем MDX в Astro (и зачем это нужно)
Что даёт MDX
MDX — это Markdown, который умеет встраивать компоненты и JS-выражения. В Astro это подключается официальной интеграцией @astrojs/mdx. ([docs.astro.build][2])
Важные моменты из официальной документации:
- интеграция расширяет Markdown JSX-компонентами и выражениями
- поддерживает frontmatter в
.mdx .mdxпишется в синтаксисе MDX, а не в “astro-html” ([docs.astro.build][2])
Установка MDX
npx astro add mdx
# или
pnpm astro add mdx
После этого в astro.config.mjs появится интеграция.
7) Пишем первую MDX-страницу
Создай файл: src/pages/guide.mdx
---
title: "Гайд по Astro"
description: "Пробная MDX-страница"
---
# MDX в Astro работает ✅
Это обычный Markdown… но с бонусами.
- списки
- `код`
- и компоненты ниже
Открой /guide — должна отрендериться страница.
8) Встраиваем компоненты в MDX (самое вкусное)
Сделаем компонент-выделялку:
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>
9) Content Collections: контент как нормальные данные (а не “файлики в папке”)
Зачем вообще collections
Чтобы:
- описать схему фронтматтера (какие поля обязательны)
- ловить ошибки на этапе билда (а не в проде)
- получать типизацию (особенно приятно в TS)
Официально: Astro использует Zod для схем коллекций и может валидировать данные + выдавать типы. ([docs.astro.build][3])
Минимальная настройка 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),
locale: z.string().default("ru"),
originalSlug: z.string().optional(),
}),
});
export const collections = { blog };
Почему
astro/zod? В документации прямо сказано, чтоzимпортируется оттуда как реэкспорт Zod. ([docs.astro.build][3])
10) Создаём первую статью в коллекции
Создай файл:
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"
locale: ru
originalSlug: "astro-part-1"
---
import Callout from "../../components/Callout.astro";
# Astro: старт (часть 1)
<Callout type="info" title="Это пост из коллекции">
Он лежит в src/content/blog и валидируется схемой.
</Callout>
11) Выводим список статей (страница /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>
12) Страница статьи по slug ( /blog/:slug )
src/pages/blog/[slug].astro
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
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>
13) Что мы уже сделали (и почему это хороший фундамент)
- Подняли проект на Astro
- Разобрали структуру папок и маршруты
- Подключили MDX и вставили компонент прямо в статью ([docs.astro.build][2])
- Настроили Content Collections с Zod-схемой (валидация + порядок) ([docs.astro.build][3])
- Собрали список постов и динамическую страницу поста
Это уже реальный блоговый фундамент, который не развалится при первом “а давайте ещё 100 статей”.



Комментарии