Персональный блог — самый популярный use-case для Astro. Неудивительно: чистый HTML, нулевой JavaScript по умолчанию, встроенные Content Collections и бесплатный деплой на Cloudflare Pages делают Astro идеальной платформой для блогинга. Этот гайд проведёт вас от npm create до живого блога.
Почему Astro — лучший выбор для блога
- PageSpeed 100/100 из коробки — важно для SEO и читателей
- MDX — пишите статьи в Markdown с поддержкой React/Astro компонентов
- Content Collections — типизированные статьи с Zod-валидацией
- Бесплатный хостинг — Cloudflare Pages, GitHub Pages
- RSS — одна интеграция, 5 минут настройки
Шаг 1: Создание проекта
npm create astro@latest my-blog
cd my-blog
npm install Выберите шаблон Blog — он уже включает базовую структуру для блога.
Или создайте с нуля и установите нужные интеграции:
npx astro add mdx sitemap Шаг 2: Структура проекта
my-blog/
├── public/
│ └── favicon.svg
├── src/
│ ├── assets/
│ │ └── covers/ # Обложки статей
│ ├── components/
│ │ ├── Header.astro
│ │ ├── Footer.astro
│ │ ├── ArticleCard.astro
│ │ └── BaseHead.astro
│ ├── content/
│ │ └── blog/ # MDX-статьи
│ │ ├── first-post.mdx
│ │ └── second-post.mdx
│ ├── layouts/
│ │ ├── BaseLayout.astro # Обёртка с head, header, footer
│ │ └── PostLayout.astro # Шаблон статьи
│ └── pages/
│ ├── index.astro # Главная
│ ├── blog/
│ │ ├── index.astro # Список статей
│ │ └── [slug].astro # Страница статьи
│ └── rss.xml.js # RSS-лента
└── astro.config.mjs Шаг 3: Content Collection для статей
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string().max(100),
description: z.string().max(160),
date: z.coerce.date(),
author: z.string().default('Автор'),
tags: z.array(z.string()).default([]),
cover: image().optional(),
coverAlt: z.string().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog }; Шаг 4: Frontmatter статьи
---
title: 'Моя первая статья в блоге на Astro'
description: 'Подробный разбор того, как я создал блог на Astro за выходные'
date: 2026-04-23
author: 'Имя Автора'
tags: ['Astro', 'Веб-разработка', 'Tutorial']
---
## Введение
Это мой первый пост на новом блоге, работающем на **Astro.js**... Шаг 5: Список статей с пагинацией
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const allPosts = await getCollection('blog', ({ data }) => !data.draft);
const sortedPosts = allPosts.sort(
(a, b) => b.data.date.getTime() - a.data.date.getTime(),
);
---
<section>
<h1>Все статьи</h1>
<ul>
{
sortedPosts.map((post) => (
<li>
<a href={`/blog/${post.id.replace('.mdx', '')}/`}>
<h2>{post.data.title}</h2>
<p>{post.data.description}</p>
<time>{post.data.date.toLocaleDateString('ru-RU')}</time>
</a>
</li>
))
}
</ul>
</section> Пагинация через встроенный paginate():
---
// src/pages/blog/[page].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }) {
const posts = await getCollection('blog', ({ data }) => !data.draft);
const sorted = posts.sort((a, b) => b.data.date - a.data.date);
return paginate(sorted, { pageSize: 10 });
}
const { page } = Astro.props;
---
{page.data.map((post) => <ArticleCard post={post} />)}
<nav>
{page.url.prev && <a href={page.url.prev}>← Назад</a>}
<span>Страница {page.currentPage} из {page.lastPage}</span>
{page.url.next && <a href={page.url.next}>Вперёд →</a>}
</nav> Шаг 6: Страница статьи
---
// src/pages/blog/[slug].astro
import { getCollection, render } from 'astro:content';
import PostLayout from '../../layouts/PostLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id.replace('.mdx', '') },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await render(post);
---
<PostLayout
title={post.data.title}
description={post.data.description}
date={post.data.date}
author={post.data.author}
>
<!-- Оглавление -->
<nav>
{
headings.map((h) => (
<a href={`#${h.slug}`} style={`padding-left: ${(h.depth - 2) * 16}px`}>
{h.text}
</a>
))
}
</nav>
<Content />
</PostLayout> Шаг 7: RSS-лента
// src/pages/rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog', ({ data }) => !data.draft);
return rss({
title: 'Мой блог на Astro',
description: 'Статьи о веб-разработке',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
description: post.data.description,
pubDate: post.data.date,
link: `/blog/${post.id.replace('.mdx', '')}/`,
})),
customData: `<language>ru-RU</language>`,
});
} Установите интеграцию: npx astro add rss
Шаг 8: Теги и фильтрация
---
// src/pages/tags/[tag].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
// Получить все уникальные теги
const allTags = [...new Set(posts.flatMap((p) => p.data.tags))];
return allTags.map((tag) => ({
params: { tag },
props: { posts: posts.filter((p) => p.data.tags.includes(tag)) },
}));
}
const { tag, posts } = Astro.props;
---
<h1>Статьи с тегом: {tag}</h1>
{posts.map((post) => <ArticleCard post={post} />)} Черновики
Добавьте поле draft: true в frontmatter — статья не попадёт в список и не будет
собираться. Идеально для незаконченных материалов.
Время чтения
Рассчитайте в [slug].astro: const readingTime = Math.ceil(post.body.split(' ').length / 200) минут.
OG-изображения
Генерируйте уникальные OG-картинки через @vercel/og или satori прямо на этапе
сборки Astro.
Деплой на Cloudflare Pages (бесплатно)
# Сборка
npm run build
# Деплой через Wrangler CLI
npx wrangler pages deploy dist/ --project-name my-blog Или подключите GitHub репозиторий в Cloudflare Pages Dashboard для автодеплоя при каждом git push.
Итог
Блог на Astro — от npm create до продакшна за один день. Content Collections дают типизированный контент, MDX позволяет вставлять интерактивные компоненты прямо в статьи, а SSG обеспечивает PageSpeed 100 без каких-либо усилий. Это лучший стек для персонального и корпоративного блогинга в 2026 году.
Следующий шаг — деплой на хостинг и настройка SEO.