← Back to blog 🇷🇺 RU

Astro в 2025–2026: быстрый старт для фронтенд-разработчика (часть 1) — структура проекта, маршруты, MDX и Content Collections

Большое и понятное введение в Astro (2025–2026): что это за фреймворк, почему он быстрый и SEO-дружелюбный, как устроен проект, как работают страницы и маршруты, как подключить MDX, и как завести Content Collections с валидацией схем (Zod).

Astro в 2025–2026: быстрый старт для фронтенд-разработчика (часть 1) — структура проекта, маршруты, MDX и Content Collections

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

Правило простое: файл = маршрут.

src/components/ — компоненты UI

Кнопки, карточки, шапки, футеры, “вставки в статью”.

src/layouts/ — лэйауты (общая обвязка страниц)

Обычно: базовый layout с <head>, шапкой/футером, контейнером, слотами.

public/ — статика “как есть”

Файлы отсюда доступны напрямую:

  • public/favicon.svg/favicon.svg
  • public/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 статей”.

0 views

Комментарии

Загрузка комментариев...