JavaScript SEO: как Google индексирует SPA и React-сайты
Двухволновая очередь рендеринга Google, ловушки CSR, разница между SSR, SSG и ISR и практические способы проверить, что JS-сайт индексируется правильно.
Как Google рендерит JavaScript: как это работает
Для обычной HTML-страницы Google делает один шаг: скачивает HTML и сразу видит контент. Для JavaScript-страницы шагов три: скачать HTML, загрузить JS-бандл и выполнить его в headless Chromium, чтобы получить итоговый DOM.
Google использует собственный браузерный движок на базе Chromium, который регулярно обновляют. Поэтому современные возможности JavaScript (ES2020+, динамический import, WebAssembly) понимает Googlebot. Проблема не в поддержке синтаксиса, а в задержке рендеринга и ограничениях ресурсов при сканировании.
Рендеринг в цифрах
Таймаут рендеринга
Если JS не выполнился примерно за 3–4 секунды, Google индексирует частично отрисованную страницу
Время CSS
Ресурсы, которые грузятся дольше 150 мс, могут блокировать рендеринг и снижать эффективность сканирования
Индексация
Контент из JS попадает в индекс с задержкой относительно HTML — иногда на дни или недели
Evergreen Google
Googlebot обновляется до последней стабильной версии Chromium — современные возможности JS поддерживаются полностью
Двухволновая индексация: почему контент из JS попадает в индекс позже
Google делит обработку на два этапа. Первая волна — сканирование: Googlebot скачивает HTML и все найденные ссылки. Вторая волна — рендеринг: бот ставит страницу в очередь, выполняет JS и индексирует итоговый DOM.
Между волнами могут пройти часы или недели — в зависимости от авторитетности сайта, краулингового бюджета и размера очереди рендеринга Google. Если ключевые тексты, H1, мета-теги или ссылки формируются только через JS, они попадут в индекс с задержкой.
CSR — главная ловушка для SEO
Client-Side Rendering (CSR) — это когда сервер отдаёт пустой <div id="root"></div>, а весь HTML строится в браузере после загрузки JS-бандла. React, Vue и Angular в режиме SPA работают так по умолчанию.
С точки зрения SEO CSR проблемна по нескольким причинам: 1) рендеринг происходит только во второй волне; 2) ресурсы, закрытые для сканера (сторонние скрипты, CDN с ограничениями в robots.txt), не загружаются, и страница рендерится не полностью; 3) любая JS-ошибка означает пустую страницу в индексе.
<!-- CSR: what Google sees in the first wave -->
<html>
<head>
<title></title> <!-- empty -->
</head>
<body>
<div id="root"></div> <!-- no content -->
<script src="/bundle.js"></script>
</body>
</html><title>, <meta name="description"> и <h1> только через клиентский JS. Google при первичном сканировании получает пустые или дефолтные значения и индексирует страницу без них.SSR, SSG и ISR: что выбрать для SEO
Решение проблем CSR — генерация HTML на сервере. Есть три основных подхода, и каждый оптимален для своего сценария.
Сравнение подходов к рендерингу
| Подход | HTML на сервере | SEO | Динамические данные |
|---|---|---|---|
| CSR | Нет | Плохо | Полные |
| SSR | Да (при каждом запросе) | Отлично | Высокие |
| SSG | Да (при сборке) | Отлично | Нет (нужна пересборка) |
| ISR | Да (по таймеру) | Отлично | Через ревалидацию |
| Подход | Лучше всего для |
|---|---|
| CSR | Авторизованные SPA, дашборды |
| SSR | Персонализированные страницы, каталоги |
| SSG | Блог, лендинги, документация |
| ISR | Каталоги с ценами, новости |
SSR (Server-Side Rendering) — HTML генерируется на сервере для каждого запроса. Максимальная свежесть данных, но выше нагрузка на сервер. В Next.js generateMetadata и async-компоненты в App Router дают SSR по умолчанию.
SSG (Static Site Generation) — HTML генерируется один раз при сборке и отдаётся как статический файл. Самый быстрый TTFB, идеально для SEO. Минус: при изменении данных нужна пересборка.
ISR (Incremental Static Regeneration) — гибрид: страницы собираются статически, но после периода ревалидации (revalidate: 3600) сервер в фоне пересобирает их при следующем запросе. В Next.js это стандартный подход для большинства страниц.
// Next.js App Router — SSG + ISR
export const revalidate = 3600; // revalidate every hour
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Product Catalogue',
description: 'Full range with up-to-date prices',
};
}
// All critical content is server-rendered:
export default async function CatalogPage() {
const products = await fetchProducts(); // server fetch
return (
<main>
<h1>Product Catalogue</h1>
{products.map(p => <ProductCard key={p.id} {...p} />)}
</main>
);
}Динамические ссылки и навигация
Googlebot понимает ссылки только в формате <a href="/url">. Ссылки, реализованные через onClick, button, data-href или window.location, для сканера невидимы. Поэтому страницы, доступные только через такую навигацию, могут не попасть в индекс.
- Используйте
<a href>для всех навигационных ссылок, а неbutton onClickсrouter.push() - Пагинация через параметры URL (
?page=2): Googlebot сканирует такие URL, если они есть в HTML - Роутер SPA:
<Link>в Next.js и<Link>в React Router на сервере генерируют корректные теги<a> - Навигация по хешу (
#anchor) не создаёт отдельных URL в индексе, не используйте её для контента, который должен ранжироваться сам по себе - Динамические меню: если пункты меню подгружаются из API после загрузки страницы, Google может не найти их до рендеринга
Ленивая загрузка и бесконечная прокрутка
Изображения с loading="lazy" Googlebot загружает: современный сканер поддерживает ленивую загрузку. Но есть условие: изображение должно быть в DOM с корректным атрибутом src при SSR. Если src задаётся JavaScript в браузере, сканер видит пустой тег.
Бесконечная прокрутка нуждается в сканируемом пути к каждому значимому элементу. Сделайте последовательные URL и обычные ссылки, которые работают без нажатия кнопки, затем проверьте через «Проверку URL» или сканирование, что HTML после рендеринга содержит нужный контент.
| Паттерн | Видит ли Googlebot? | Рекомендация |
|---|---|---|
| <img src="/img.webp" loading="lazy"> | Да | Используйте |
| <img data-src="/img.webp"> (JS задаёт src) | Нет | Избегайте |
| Бесконечная прокрутка без URL | Только первый экран | Добавьте пагинацию |
| Контент за кнопкой «Показать ещё» | Нет | Вынесите часть в HTML |
| Табы со скрытым контентом | Да, с задержкой | Продублируйте в HTML |
Диагностика: как проверить рендеринг JavaScript
Ключевой вопрос диагностики: «Что видит Googlebot на моей странице?» Для ответа есть несколько инструментов.
Инструменты диагностики
- Google Search Console → Проверка URL: показывает скриншот страницы после рендеринга Googlebot, список ресурсов и найденных ссылок. Самый прямой способ проверки.
- «Просмотр как Googlebot» в GSC: запускает сканер и возвращает HTML после рендеринга. Сравните его с исходным HTML.
curlилиwget: получают сырой HTML без JS. Если ключевые мета-теги и контент есть в выводеcurl, они попадут в первую волну индексации.- Отключите JS в Chrome DevTools (F12 → Ctrl+Shift+P → «Disable JavaScript»): быстрый способ увидеть страницу так, как её видит сканер без рендеринга.
- Screaming Frog с рендерингом: настройка «Spider > Configuration > Rendering > Googlebot» позволяет сканировать сайт с рендерингом JS и видеть ссылки, найденные после рендеринга.
# Check what Google sees without JS rendering:
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page
# Find all title / meta description / h1 in HTML response:
curl -s https://example.com/page | grep -E '<title>|<meta name="description"|<h1'Чеклист JavaScript SEO
Ключевые проверки
- Title и meta description присутствуют в серверном HTML-ответе (SSR/SSG), а не генерируются JS
- H1, H2 и основной текст доступны без JavaScript
- Навигационные ссылки используют
<a href>, а неonClick/button - Структурированные данные (JSON-LD) находятся в
<head>в HTML-ответе - Изображения имеют статический
srcв HTML, аloading="lazy"используется только для изображений, не являющихся LCP - Robots.txt не блокирует JS- и CSS-файлы, нужные для рендеринга
- Нет JS-ошибок, которые ломают рендеринг (проверьте консоль Chrome DevTools на странице)
- Core Web Vitals: LCP, INP и CLS соответствуют пороговым значениям Google
- Пагинация через параметры URL, а не только через состояние JS
- Канонические URL (
<link rel="[canonical](/glossary/canonical)">) прописаны в HTML, а не заданы через JS
Частые вопросы
Может ли Google вообще индексировать JavaScript?
Хорошо ли индексируется сайт на React/Next.js автоматически?
Нужен ли пререндеринг или динамический рендеринг?
Можно ли использовать React.lazy и разбиение кода?
Как проверить, что сайт индексируется правильно?
Check it with SpiderHead
Free and local: run the audit on your own machine.



