Проект на Astro вырос из небольшого интерфейса в большой продукт. Какие архитектурные проблемы вы ожидаете увидеть?
При росте Astro-проекта возникают: расползание клиентских островков (потеря главного преимущества), отсутствие глобального state management, монолитная структура роутов, дублирование data-fetching логики и деградация времени сборки при тысячах статических страниц.
Контекст: от MVP к продукту
Astro идеально подходит для контентных сайтов с небольшим количеством интерактивности. Когда продукт вырастает — появляются формы, аутентификация, real-time обновления, сложные фильтры — архитектура начинает трещать по швам. Ниже — типичные болезни роста и способы их лечения.
1. Расползание клиентских островков
Изначально на странице один островок — хедер с меню. Потом добавляется поиск, фильтры, корзина, чат. Каждый компонент тянет свой фреймворк. Итог: страница загружает React, Vue и Svelte одновременно.
- Зафиксируйте правило в архитектурном решении (ADR): один UI-фреймворк на проект.
- Регулярно запускайте
astro build --verboseи отслеживайте рост клиентского JS. - Рассмотрите переход на
output: 'hybrid'и явный список страниц сprerender = true— это сохраняет статику там, где она нужна.
2. Отсутствие глобального состояния
Острова изолированы по определению. Когда пользователь авторизуется в одном острове, второй об этом не знает.
Решение — nanostores (официально рекомендованы командой Astro):
// src/stores/auth.ts
import { atom, computed } from 'nanostores';
export const $user = atom(null);
export const $isAuthenticated = computed($user, (user) => user !== null);
// src/components/Header.tsx (React island)
import { useStore } from '@nanostores/react';
import { $user } from '../stores/auth';
export function Header() {
const user = useStore($user);
return <div>{user ? user.name : 'Login'}</div>;
}
Для сервер-клиент синхронизации состояния сессии используйте cookies и серверное чтение в Astro.locals через middleware.
3. Монолитная структура роутов
Сотни файлов в src/pages/ превращаются в неуправляемое дерево. Решение — файловая группировка и Content Collections:
src/
pages/
blog/[slug].astro
docs/[...slug].astro
content/
blog/ # .md/.mdx файлы
docs/
collections/
blog.ts # схема + query
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
export const collections = {
blog: defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
pubDate: z.date(),
tags: z.array(z.string()).default([]),
}),
}),
};
4. Дублирование data-fetching логики
В frontmatter каждой страницы копируется один и тот же fetch к API. При изменении endpoint нужно обновлять десятки файлов.
Решение — выносить fetching в отдельный слой:
// src/lib/api/posts.ts
export async function getPost(slug: string) {
const res = await fetch(`${import.meta.env.API_URL}/posts/${slug}`);
if (!res.ok) throw new Error(`Post not found: ${slug}`);
return res.json();
}
// src/pages/blog/[slug].astro
---
import { getPost } from '../../lib/api/posts';
const { slug } = Astro.params;
const post = await getPost(slug);
---
5. Деградация времени сборки
При 10 000+ статических страниц astro build может занимать 20+ минут. Стратегии:
- Переход на
output: 'hybrid': тяжёлые разделы переводятся на SSR, критичные остаются статическими. - Incremental builds через внешний кеш: Vercel и Netlify поддерживают кеш сборки, Astro записывает кеш в
.astro/. - Параллельный content processing: используйте Workers в
astro.config.mjsчерезvite.worker.
6. Слабость типизации между сервером и клиентом
Props, передаваемые в клиентский компонент, сериализуются в JSON. Если TypeScript-тип содержит Date или кастомный класс, на клиент придёт строка, и типы соврут.
// Ловушка: Date сериализуется в строку
<MyComponent client:load startDate={new Date()} />
// В компоненте props.startDate будет string, не Date
// Решение: явная типизация
interface Props {
startDate: string; // ISO string
}
Подводные камни
- Middleware накапливает побочные эффекты: с ростом команды в
src/middleware.tsнакапливаются проверки auth, логирование, A/B-тесты. Без явного порядка и документации middleware превращается в «чёрный ящик». - Content Collections и горячая замена: при изменении схемы коллекции в
src/content/config.tsнужно перезапускать dev-сервер — HMR схему не подхватывает. - Server Islands (Astro 5+) vs. обычные острова: Server Islands рендерятся на сервере по запросу, но не кешируются автоматически. Добавить кеш можно через
cache-заголовки или платформенный KV. - Отсутствие встроенного роутинга на клиенте: View Transitions API — не полноценный клиентский роутер. Для сложных SPA-переходов всё равно нужен дополнительный инструмент или переосмысление архитектуры.
- Рост зависимостей интеграций: каждая интеграция (
@astrojs/react,@astrojs/sitemap,@astrojs/image) имеет собственный lifecycle. После major-обновления Astro часть интеграций ломается раньше, чем выходят совместимые версии. - Тестирование серверных компонентов:
.astro-файлы нельзя тестировать через Jest/Vitest напрямую. Бизнес-логика обязана быть вынесена в.ts-модули — иначе покрытие тестами будет нулевым.
What hurts your answer
- Говорить только о запуске Astro, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Astro
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию