Astro 5 Islands: SEO-лендинг с View Transitions
Маркетинговый сайт из пяти–десяти страниц — классический кейс, где React- или Vue-SPA проигрывает по Core Web Vitals: весь фреймворк грузится ради одной кнопки «Записаться». SSR на Next.js решает SEO, но TTFB растёт, а edge-кэш сложнее настроить. Astro 5 предлагает другой компромисс: HTML генерируется статически, JavaScript подключается точечно — только к «островам» (Islands), которым нужна интерактивность.
В Astro 5 появились View Transitions — плавные переходы между страницами без полноценного SPA. Пользователь видит анимацию смены экрана, поисковый робот — обычный HTML с уникальным <title> и canonical на каждой странице. Это не противоречие: View Transitions работают поверх MPA (multi-page application), а не заменяют его.
В статье соберём production-лендинг услуг: главная, прайс, контакты, форма с shadcn/ui через React-island, hero с framer-motion, JSON-LD и деплой на Vercel.
Зачем Islands на лендинге, а не SPA или SSR
Архитектура Islands — главная идея Astro. Сервер (или сборщик) отдаёт чистый HTML. Компоненты с директивой client:* гидратируются изолированно; остальная страница остаётся статической разметкой без JavaScript.
| Подход | JS на первом экране | SEO | Переходы между страницами |
|---|---|---|---|
| SPA (React/Vite) | Весь бандл | Требует SSR/prerender | Мгновенные, но тяжёлые |
| SSR (Next.js) | Зависит от RSC/CSR | Отличный | Навигация через router |
| Astro Islands | Только острова | Полный HTML из коробки | MPA + View Transitions |
Для лендинга услуг типичное распределение:
Без JavaScript (статический HTML):
- Hero с заголовком, подзаголовком, текстом CTA
- Блок преимуществ, отзывы, FAQ
- Footer, навигация, breadcrumbs
- Open Graph, canonical, JSON-LD
С JavaScript (Islands):
- Форма записи с валидацией (React + shadcn/ui)
- Калькулятор стоимости
- Анимированный hero (framer-motion)
- Cookie-баннер, A/B-блок по UTM
Lighthouse на такой схеме стабильно даёт LCP < 1.5 s и TBT близкий к нулю, потому что основной контент не ждёт гидратации.
Структура проекта Astro 5
Создаём проект с React-интеграцией — для shadcn/ui и framer-motion:
npm create astro@latest landing -- --template basics
cd landing
npx astro add react
npx astro add tailwind
Типичная структура лендинга:
src/
├── layouts/
│ └── BaseLayout.astro # <head>, View Transitions, JSON-LD
├── pages/
│ ├── index.astro # главная
│ ├── pricing.astro # прайс
│ └── contact.astro # контакты + форма
├── components/
│ ├── Hero.astro # статический hero
│ ├── HeroAnimation.tsx # framer-motion island
│ ├── BookingForm.tsx # React island
│ └── Faq.astro
└── content/
└── services/ # Content Layer (опционально)
└── web-dev.md
Базовый layout с View Transitions:
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site);
---
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<title>{title}</title>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
ViewTransitions вставляет клиентский скрипт (~2 KB gzip), который перехватывает клики по внутренним ссылкам и анимирует смену DOM. При отключённом JS браузер работает как обычный MPA — SEO не страдает.
Если нужна подобная разработка — напишите в Telegram.
View Transitions: анимация без SPA
View Transitions API изначально появился в браузерах; Astro 5 оборачивает его в <ViewTransitions /> и даёт fallback для Safari через @astrojs/transitions. Переходы настраиваются на уровне layout или отдельных элементов.
Именованные переходы для hero-секции
Чтобы hero плавно морфился между главной и прайсом, помечаем элемент transition:name:
---
// src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Разработка сайтов — Astro, Next.js" description="...">
<header transition:name="hero" transition:animate="fade">
<h1>Быстрые SEO-лендинги</h1>
</header>
<!-- остальной контент -->
</BaseLayout>
---
// src/pages/pricing.astro
---
<BaseLayout title="Цены — разработка сайтов" description="...">
<header transition:name="hero" transition:animate="fade">
<h1>Тарифы</h1>
</header>
</BaseLayout>
Одинаковый transition:name="hero" связывает элементы на разных страницах — браузер интерполирует позицию и размер. Для SEO каждая страница сохраняет свой <h1> и уникальный title; робот индексирует их независимо.
Prefetch для мгновенного ощущения
Astro 5 prefetch загружает HTML соседних страниц при наведении на ссылку:
<a href="/pricing" data-astro-prefetch>Цены</a>
Prefetch не подгружает JavaScript островов заранее — только HTML. Это сохраняет экономию трафика и не ухудшает INP на текущей странице.
Islands: React, shadcn/ui и framer-motion
Директива client:visible — оптимальный выбор для форм и анимаций ниже первого экрана: JS загружается, когда блок попадает во viewport.
---
// src/pages/contact.astro
import BookingForm from '../components/BookingForm.tsx';
---
<section id="booking">
<h2>Записаться на консультацию</h2>
<BookingForm client:visible />
</section>
Для hero-анимации на первом экране используем client:load — компонент гидратируется сразу, но изолирован от остальной страницы:
---
import HeroAnimation from '../components/HeroAnimation.tsx';
---
<HeroAnimation client:load />
// src/components/HeroAnimation.tsx
import { motion } from 'framer-motion';
export default function HeroAnimation() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
{/* декоративные элементы — не SEO-критичный контент */}
</motion.div>
);
}
Важно для SEO: текст заголовка и описания держим в .astro-файле, а не внутри framer-motion. Анимация — декоративный слой; робот читает статический HTML.
shadcn/ui подключается как обычный React-компонент в island. Tailwind-конфиг общий для Astro и React — классы не дублируются.
Content Layer для FAQ и кейсов
Astro 5 Content Layer позволяет хранить тексты в Markdown/MDX и генерировать статические страницы:
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
const faq = defineCollection({
type: 'content',
schema: z.object({
question: z.string(),
order: z.number(),
}),
});
export const collections = { faq };
Контент рендерится в HTML при сборке — идеально для FAQ-блоков, которые должны попасть в индекс без client-side fetch.
SEO: metadata, JSON-LD и Core Web Vitals
View Transitions не меняют правила SEO — они лишь улучшают UX. Базовый чеклист остаётся прежним.
Уникальные meta на каждой странице
Каждый .astro-файл передаёт title и description в layout. Canonical строится из Astro.site + pathname — дубликаты не попадают в индекс.
JSON-LD для услуг
Structured data — Server-side в Astro по умолчанию:
---
const schema = {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: 'Разработка сайтов',
url: Astro.site,
areaServed: 'RU',
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
JSON-LD в <head> или перед </body> — в статическом HTML, виден Googlebot без JavaScript.
Core Web Vitals: что проверять
| Метрика | Цель | Как Astro помогает |
|---|---|---|
| LCP | < 2.5 s | Статический HTML, без блокирующего JS |
| INP | < 200 ms | Islands не блокируют main thread целиком |
| CLS | < 0.1 | Резерв места под island через skeleton |
PageSpeed Insights: прогоняйте каждую страницу отдельно (/, /pricing, /contact). View Transitions влияют только на клиентскую навигацию — первый заход оценивается как обычный MPA.
Google Search Console: «Проверка URL» покажет rendered HTML. Убедитесь, что <title>, canonical и тексты FAQ видны в снимке.
Деплой на Vercel и preview-окружения
Astro 5 деплоится на Vercel одной командой:
npm run build
# output: dist/
Vercel определяет Astro автоматически. Статические файлы раздаются с Edge Network; vercel.json для редиректов или заголовков безопасности:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" }
]
}
]
}
Preview-деплои на каждый PR — удобно проверять View Transitions и Lighthouse до merge. Production URL отдаёт Cache-Control для статики; HTML страниц кэшируется на edge после деплоя.
Альтернатива — Cloudflare Pages или Netlify. Islands-архитектура одинаково работает везде, где есть static hosting.
Нужна помощь? Telegram → или vic.kell@ya.ru
FAQ
View Transitions ломают SEO и индексацию?
Нет. Каждая страница — отдельный HTML-документ с уникальным URL, title и canonical. View Transitions активируются только при клиентской навигации. Googlebot получает полный HTML при первом запросе; prefetch не меняет canonical.
Когда Islands хуже, чем Next.js 15 с PPR?
Если нужна одна страница с несколькими динамическими блоками (форма + калькулятор + персонализация по cookie) и команда уже на React — Next.js 15 с Partial Prerendering может быть проще. Astro выигрывает на многостраничных маркетинговых сайтах, где большинство контента статично.
Можно ли подключить headless CMS?
Да. Sanity, Contentful, Strapi — fetch на этапе сборки (getStaticPaths / Content Layer). Контент попадает в HTML; revalidate через webhook + CI redeploy или @astrojs/vercel с ISR-подобным поведением на adapter-уровне.
client:visible или client:idle для формы?
client:visible — для блоков ниже fold: экономит JS на первом экране. client:idle — если форма в hero и должна быть интерактивной быстро, но не блокировать LCP. Избегайте client:only на SEO-критичном контенте — он не рендерится на сервере.
framer-motion не убивает INP?
Изолированный island с framer-motion не блокирует гидратацию всей страницы. Держите анимацию декоративной, используйте will-change умеренно, тестируйте INP в Chrome DevTools Performance. Текст hero — в статическом Astro, не в motion-компоненте.
Заключение
Astro 5 с Islands и View Transitions — сильная связка для SEO-лендингов и небольших маркетинговых сайтов. Статический HTML обеспечивает индексацию и высокие Core Web Vitals; JavaScript подключается точечно к формам и анимациям. View Transitions дают SPA-подобный UX без SPA-оверхеда.
Практическая схема: тексты и SEO-разметка в .astro, интерактив в React-islands с client:visible / client:load, JSON-LD в layout, деплой статики на Vercel. Для одностраничного лендинга с тяжёлой динамикой смотрите Next.js 15 PPR — для классического многостраничного сайта Astro остаётся более лёгким выбором в 2026 году.