Перейти к содержимому

6. Архитектура Islands

Islands Architecture — это архитектурный паттерн для веб-приложений, предложенный Katie Sylor-Miller и популяризованный Jason Miller (создатель Preact). Astro стал первым крупным фреймворком, реализовавшим этот паттерн как основную концепцию.

Идея проста и гениальна: большинство страниц на большинстве сайтов статичны, и только небольшие, изолированные области требуют интерактивности. Зачем загружать JavaScript для всей страницы, если можно загрузить его только там, где он действительно нужен?

В традиционных React/Next.js/Nuxt приложениях используется полная гидратация:

  1. Сервер рендерит HTML-страницу
  2. Браузер загружает весь JavaScript-бандл (часто 200KB — 1MB+)
  3. React “гидратирует” всю страницу — воссоздаёт дерево компонентов в памяти
  4. Страница становится интерактивной

Проблема: даже статичный текст требует загрузки и выполнения JavaScript для гидратации. Это называется “налогом на JavaScript”.

Типичная страница блога — чего хотим:
┌─────────────────────────────┐
│ Шапка (Nav) [статично] │ 0 KB JS
├─────────────────────────────┤
│ Заголовок [статично] │ 0 KB JS
│ Текст статьи [статично] │ 0 KB JS
│ Изображения [статично] │ 0 KB JS
├─────────────────────────────┤
│ Форма подписки [динамично]│ ~15 KB JS
├─────────────────────────────┤
│ Комментарии [динамично]│ ~30 KB JS
├─────────────────────────────┤
│ Футер [статично] │ 0 KB JS
└─────────────────────────────┘
ИТОГО: ~45 KB JS
Реальность без Islands (Next.js по умолчанию):
Вся страница = ~300 KB JS для гидратации

С Islands Architecture только интерактивные компоненты получают JavaScript:

┌─────────────────────────────┐
│ Шапка (Nav) ░░░░░░░░░░ │ ← Чистый HTML
├─────────────────────────────┤
│ Заголовок ░░░░░░░░░░ │ ← Чистый HTML
│ Текст статьи ░░░░░░░░░░ │ ← Чистый HTML
├─────────────────────────────┤
│ ┌─ 🏝️ ОСТРОВ ─────────────┐ │
│ │ Форма подписки │ │ ← ~15 KB JS
│ └─────────────────────────┘ │
├─────────────────────────────┤
│ ┌─ 🏝️ ОСТРОВ ─────────────┐ │
│ │ Комментарии │ │ ← ~30 KB JS
│ └─────────────────────────┘ │
├─────────────────────────────┤
│ Футер ░░░░░░░░░░ │ ← Чистый HTML
└─────────────────────────────┘
ИТОГО: ~45 KB JS (только острова!)

Результат: браузер загружает только тот JavaScript, который действительно нужен.

Astro управляет гидратацией через client-директивы:

---
import Counter from '../components/Counter.jsx';
import SearchBar from '../components/SearchBar.jsx';
import HeavyChart from '../components/HeavyChart.jsx';
import Tooltip from '../components/Tooltip.jsx';
import MobileMenu from '../components/MobileMenu.jsx';
---
<!-- Гидратировать сразу при загрузке страницы -->
<SearchBar client:load />
<!-- Гидратировать когда браузер не занят (idle) -->
<!-- Отлично для некритичных компонентов -->
<Counter client:idle />
<!-- Гидратировать когда компонент виден во viewport -->
<!-- Идеально для контента ниже fold -->
<HeavyChart client:visible />
<!-- Гидратировать при совпадении media query -->
<MobileMenu client:media="(max-width: 640px)" />
<!-- Гидратировать только на клиенте (без SSR) -->
<SomeDynamicWidget client:only="react" />
<!-- Статичный компонент — НИКАКОГО JavaScript -->
<Tooltip />
ДирективаКогда применять
client:loadКритически важная интерактивность (навигация, форма авторизации)
client:idleНекритичные виджеты (счётчик, лайки)
client:visibleКонтент ниже fold (комментарии, heavy charts)
client:mediaКомпоненты для конкретных экранов
client:onlyКомпоненты без серверного рендеринга
(без директивы)Полностью статичный HTML, 0 KB JS

Astro позволяет смешивать фреймворки — каждый остров может использовать свой:

---
import ReactSearch from './SearchBar.jsx'; // React
import VueComments from './Comments.vue'; // Vue
import SvelteCounter from './Counter.svelte'; // Svelte
import StaticCard from './Card.astro'; // Astro (без JS)
---
<StaticCard /> <!-- 0 KB JS -->
<ReactSearch client:load /> <!-- React JS -->
<VueComments client:visible /> <!-- Vue JS -->
<SvelteCounter client:idle /> <!-- Svelte JS -->

Каждый остров — это изолированный микроприложение, независимое от других.

src/pages/blog/[slug].astro
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/BlogLayout.astro';
import LikeButton from '../../components/LikeButton.jsx'; // React
import Comments from '../../components/Comments.vue'; // Vue
import Newsletter from '../../components/Newsletter.svelte'; // Svelte
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({ params: { slug: post.slug }, props: { post } }));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Layout title={post.data.title}>
<!-- Статичная часть — 0 KB JS -->
<h1>{post.data.title}</h1>
<time>{post.data.date.toLocaleDateString()}</time>
<!-- Markdown контент — 0 KB JS -->
<Content />
<!-- Острова — JS только здесь -->
<LikeButton postId={post.id} client:visible />
<Newsletter client:idle />
<Comments postSlug={post.slug} client:visible />
</Layout>

Сравнение страницы блога на разных фреймворках:

МетрикаGatsbyNext.jsAstro (Islands)
JS на клиенте~300 KB~180 KB~5-50 KB
Time to Interactive4.2s2.8s0.8s
Lighthouse Score75-8580-9095-100
LCP3.1s2.2s0.9s

Посмотрите как работает Islands Architecture. Переключайте компоненты между “статичным” и “островом”: