← Назад в блог

Astro 2025–2026 (часть 2): islands, гидратация, View Transitions и SEO

Практический гайд по Astro: islands architecture, выборочная гидратация, View Transitions, SEO (canonical, OpenGraph, sitemap, RSS). Быстрый сайт без лишнего JS.

Astro 2025–2026 (часть 2): islands, гидратация, View Transitions и SEO

Требования

  • Node.js LTS (18+)
  • Базовые знания HTML/CSS/JS
  • Опыт работы с Astro (рекомендуется прочитать часть 1)

Astro в 2025–2026 (часть 2): islands, гидратация, View Transitions и SEO

В первой части мы сделали фундамент: страницы, layout, MDX и Content Collections. Во второй — самое “астровское”: почему Astro быстрый и как не превратить сайт в SPA по привычке.

План:

  • Islands architecture: что это и зачем
  • client:* директивы: когда и как грузить интерактив
  • server:defer: когда “динамику” лучше оставить на сервере
  • View Transitions: как сделать плавную навигацию без SPA
  • SEO-минимум для блога: canonical + OG + sitemap + RSS

1) Islands architecture: главная идея Astro

Islands architecture — это подход, где основная часть страницы рендерится как быстрый статический HTML, а JavaScript добавляется маленькими “островами” только туда, где нужна интерактивность (например, карусель, поиск, комментарии). Это снижает “монолитный” JS-пакет, который обычно тормозит загрузку у SPA.

Что важно понять джуну (и полезно помнить сеньору)

  • В Astro по умолчанию UI-компоненты рендерятся в HTML и CSS и не отправляют клиентский JS.
  • Интерактивность включается явно, через client:* директиву.
  • Ты сам задаёшь “приоритет”: что грузить сразу, что позже, что только если пользователь докрутил.

Это и есть “скорость по умолчанию”: пока ты не сказал «давай JS», Astro не тащит его в браузер просто потому что “так принято”.


2) Клиентские директивы client:*: как управлять гидратацией

Astro даёт набор template directives (спец-атрибутов), которые видит компилятор и меняет поведение компонента.

Нас интересуют client directives:

  • client:load — гидратация сразу при загрузке
  • client:idle — когда браузер “подустал и освободился”
  • client:visible — когда компонент попал в viewport
  • client:media — по media-query
  • client:only — рендер только на клиенте (SSR/SSG не отдаст HTML-разметку компонента)

2.1) Правило большого пальца

  • Интерактив выше фолда и критичен (меню, переключатель темы, поиск) → client:load
  • Не критичен (виджеты, “похожие статьи”, графики) → client:idle
  • Ниже фолда (комменты, тяжелый блок) → client:visible
  • Нужно только на мобилке/десктопеclient:media
  • Компонент вообще не должен SSR/SSG (например, жёсткая зависимость от window) → client:only

3) Практика: подключаем интерактивный компонент как остров

3.1) Ставим UI-фреймворк (пример: React)

Если ты уже используешь React-компоненты, подключай интеграцию (официальный путь):

pnpm astro add react
# или npm/yarn аналогично

Дальше — создаём компонент и подключаем его как остров.

3.2) Пример: счётчик на React (минимально интерактивный)

src/components/Counter.jsx:

import { useState } from "react";

export default function Counter({ initial = 0 }) {
  const [count, setCount] = useState(initial);
  return (
    <div style={{ display: "flex", gap: 12, alignItems: "center" }}>
      <button onClick={() => setCount((c) => c - 1)}>-</button>
      <strong>{count}</strong>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
    </div>
  );
}

Теперь подключаем на странице Astro.

src/pages/playground.astro:

---
import BaseLayout from "../layouts/BaseLayout.astro";
import Counter from "../components/Counter.jsx";
---

<BaseLayout title="Playground" description="Islands demo">
  <h1>Islands demo</h1>

  <!-- По умолчанию Counter отрендерится в HTML, но НЕ будет интерактивным -->
  <!-- Делаем его островом: -->
  <Counter client:visible initial={10} />

  <p style="opacity:.7">
    Пока пользователь не докрутил — JS не грузится.
  </p>
</BaseLayout>

Вот это и есть “выборочная гидратация”: страница лёгкая, интерактив появляется ровно там и тогда, где он нужен. (docs.astro.build)


4) client:media: интерактив только для нужного устройства

Кейс: на мобилке меню должно быть интерактивным (бургер), а на десктопе — обычная разметка.

---
import MobileMenu from "../components/MobileMenu.jsx";
---

<MobileMenu client:media="(max-width: 768px)" />

В итоге:

  • на десктопе этот JS вообще не нужен → и не будет грузиться (в рамках логики директив)
  • на мобилке — загрузится, когда media-query совпадёт (docs.astro.build)

5) client:only: когда SSR/SSG противопоказан

client:only — это “я не хочу SSR/SSG HTML для этого компонента, пусть живёт только в браузере”.

Пример: компонент, который на старте обращается к window (и ты не хочешь/не можешь сделать graceful fallback).

---
import HeavyWidget from "../components/HeavyWidget.jsx";
---

<HeavyWidget client:only="react" />

Важно: это самый дорогой режим, потому что без SSR HTML будет более пустым, а контент появится позже. Используй только когда реально надо. (docs.astro.build)


6) Server islands: server:defer (когда динамика — на сервере, но не мешает странице)

Astro поддерживает “server islands”: можно вынести дорогую серверную часть так, чтобы основной HTML пришёл быстро, а серверная вставка догрузилась параллельно. Это делается директивой server:defer. (docs.astro.build)

Пример (условный): блок “Персональные рекомендации”.

src/components/Recommendations.astro:

---
const res = await fetch("https://example.com/api/reco"); // пример
const items = await res.json();
---

<section>
  <h2>Рекомендации</h2>
  <ul>
    {items.map((x) => <li>{x.title}</li>)}
  </ul>
</section>

На странице:

---
import Recommendations from "../components/Recommendations.astro";
---

<Recommendations server:defer />

Смысл: не блокировать основной контент “тяжёлой” серверной логикой. (docs.astro.build)


7) View Transitions: плавные переходы без SPA-цирка

View Transitions — это анимации между страницами. В Astro их надо включить явно: по умолчанию навигация обычная, “полная перезагрузка документа”. (docs.astro.build)

У Astro есть два подхода:

  1. Browser-native cross-document view transitions (MPA) — анимация, но без SPA-роутинга и без лишнего JS.
  2. Astro <ClientRouter /> — клиентская навигация + дополнительные возможности, но уже с JS. (docs.astro.build)

7.1) Вариант А: просто включить View Transitions (MPA-режим)

В layout (обычно в <head>) добавь компонент переходов.

src/layouts/BaseLayout.astro:

---
import { ViewTransitions } from "astro:transitions";

const { title = "Site", description = "" } = 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} />

    <ViewTransitions />
  </head>

  <body>
    <slot />
  </body>
</html>

Это включит переходы (где поддерживается API) и сохранит сайт как MPA. (docs.astro.build)

7.2) Вариант B: <ClientRouter /> (когда нужна клиентская навигация)

Если тебе нужно поведение “почти SPA” (с нюансами), можно использовать <ClientRouter />. В документации прямо объясняют различия между нативными переходами и Astro-роутером. (docs.astro.build)

Я бы советовал:

  • для блога/доков начать с нативных transitions
  • <ClientRouter /> брать, когда реально упёрся в потребность клиентского роутинга

8) SEO-минимум для Astro-блога (без шаманства)

Сейчас соберём “обязательный набор”, который реально работает:

  • site в конфиге
  • canonical + OpenGraph
  • sitemap
  • RSS + автообнаружение

9) site в astro.config.mjs: основа для sitemap и RSS

И sitemap, и RSS часто требуют знать боевой домен.

astro.config.mjs:

import { defineConfig } from "astro/config";
import sitemap from "@astrojs/sitemap";

export default defineConfig({
  site: "https://example.com",
  integrations: [sitemap()],
});

@astrojs/sitemap прямо требует sitehttp/https) для генерации sitemap. (docs.astro.build) Для RSS в рецепте тоже есть совет: убедись, что site настроен, чтобы генерировать ссылки. (docs.astro.build)


10) Sitemap через @astrojs/sitemap

Самый простой путь:

pnpm astro add sitemap
# или npm/yarn

Интеграция генерирует sitemap на build и умеет учитывать статические маршруты и динамические, построенные через getStaticPaths(). (docs.astro.build)

Важное ограничение: в SSR-режиме интеграция не сможет сама сгенерировать записи для динамических роутов. (docs.astro.build)


11) RSS через @astrojs/rss: делаем /rss.xml

Astro предлагает рецепт через API endpoint (файл в src/pages/ с расширением .xml.js). (docs.astro.build)

11.1) Установка

pnpm add @astrojs/rss
# или npm/yarn

11.2) Генерация RSS из Content Collections

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"))
    .filter((p) => !p.data.draft)
    .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

  return rss({
    title: "VProger Blog",
    description: "Заметки о разработке, DevOps и Astro",
    site: context.site, // берётся из astro.config.mjs (site)
    items: posts.map((post) => ({
      title: post.data.title,
      description: post.data.description,
      pubDate: post.data.pubDate,
      link: `/blog/${post.data.slug}/`,
    })),
  });
}

Рецепт @astrojs/rss официально описывает процесс установки, необходимость site и подход через .xml.js endpoint. (docs.astro.build)

11.3) Автообнаружение RSS (чтобы браузеры/ридеры нашли)

Добавь в <head>:

<link
  rel="alternate"
  type="application/rss+xml"
  title="RSS"
  href="/rss.xml"
/>

В рецепте RSS есть отдельный пункт про auto-discovery. (docs.astro.build)


12) Canonical + OpenGraph: делаем один компонент и забываем

Создай src/components/SeoHead.astro:

---
const {
  title,
  description,
  canonical,
  ogImage,
} = Astro.props;

const url = canonical ?? Astro.url?.href;
---

<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />

<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={url} />

{ogImage ? <meta property="og:image" content={ogImage} /> : null}

Использование в layout:

---
import SeoHead from "../components/SeoHead.astro";
const { title = "Site", description = "", ogImage } = Astro.props;
---

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <SeoHead
    title={title}
    description={description}
    ogImage={ogImage}
  />
</head>

Теперь на страницах ты просто передаёшь title/description/ogImage, и SEO не размазывается по проекту.


13) Итог части 2

Ты теперь понимаешь и умеешь:

  • почему Astro быстрый: острова + выборочная гидратация (docs.astro.build)
  • как управлять интерактивом через client:* директивы (docs.astro.build)
  • как включить View Transitions без превращения сайта в SPA (docs.astro.build)
  • как собрать SEO-базу: site, sitemap, RSS (docs.astro.build)
Предыдущая часть: Astro в 2025–2026: быстрый старт для фронтенд-разработчика (часть 1) — структура проекта, маршруты, MDX и Content Collections
0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!