AstroSeniorExperience

Проект на 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 и безопасность
  • Умеет ранжировать риски по вероятности и влиянию

Related topics