Что такое JavaScript SEO

JavaScript SEO — это часть технического SEO, которая отвечает за то, чтобы страницы, контент, ссылки, мета-теги, canonical, robots-директивы и structured data, создаваемые или изменяемые JavaScript, были доступны поисковым системам для обхода, рендеринга и индексации.

Главный риск не в React, Vue или Angular, а в моменте появления SEO-сигналов. Если H1, основной текст, внутренние ссылки, canonical или schema.org появляются только после клиентского рендеринга, бот может увидеть страницу неполной, с задержкой или иначе, чем пользователь.

Коротко: JavaScript SEO — это контроль расхождений между raw HTML, rendered HTML и bot view.

Поэтому один сайт на React отлично ранжируется, а другой теряет половину индекса. Технология не виновата. Виноват конкретный способ её использования.

Кому нужна JavaScript SEO-диагностика

JS SEO-диагностика нужна не каждому сайту с JavaScript, а проектам, где поисковый бот может видеть меньше, чем пользователь.

  • Сайт сделан на React, Vue, Angular, Nuxt, Next.js или похожем JS-фреймворке
  • Это SPA или PWA с клиентской навигацией
  • Основной контент появляется после загрузки страницы
  • Фильтры, пагинация, карточки, отзывы, табы или похожие блоки работают через JavaScript
  • В GSC страницы есть, но текст, ссылки или сниппеты выглядят странно
  • Краулер видит меньше страниц, ссылок или текста, чем пользователь в браузере
  • Мобильная версия содержит меньше контента, чем desktop
Коротко: если сайт выглядит полноценным в браузере, но в View Source, GSC или краулере теряет текст, ссылки, canonical, noindex или schema.org, нужна JavaScript SEO-диагностика.

Что не является JavaScript SEO-проблемой

JavaScript сам по себе не мешает SEO, если критичный контент, ссылки, мета-теги и статусы доступны боту. JS можно спокойно использовать для интерфейса, интерактивных элементов, фильтров, личных кабинетов, калькуляторов и клиентских сценариев, если индексируемая версия страницы остаётся понятной поисковику.

Быстрая проверка JavaScript SEO за 5 минут

Мини-чек-лист
  1. Откройте View Source и проверьте, есть ли H1, title, description, canonical и основной текст.
  2. Откройте DevTools → Elements и сравните rendered DOM с исходным HTML.
  3. Проверьте URL в GSC URL Inspection и посмотрите, что видел Googlebot.
  4. Сравните количество внутренних ссылок в raw HTML и rendered HTML.
  5. Проверьте, нет ли noindex или canonical, которые меняются после JavaScript.
  6. Откройте страницу с мобильным User-Agent и проверьте, не отличается ли состав контента.
  7. Проверьте Console и Network на JS-ошибки, таймауты и 4xx/5xx по ресурсам.
Коротко: минимальная диагностика JavaScript SEO строится на сравнении трёх версий страницы: raw HTML, rendered HTML и bot view в Google Search Console. Всё, что отличается между этими версиями, становится списком потенциальных SEO-проблем.

Как Googlebot обрабатывает JavaScript

Googlebot понимает JavaScript. Но не так, как браузер пользователя, и не мгновенно.

Упрощённая модель процесса:

01

Скачивание исходного HTML

Бот получает HTML от сервера. Это то, что вы видите в View Source.

02

Очередь рендеринга (WRS)

Страница ставится в очередь Web Rendering Service. Рендеринг происходит позже — срок зависит от приоритета ресурса.

03

Задержка рендеринга

Задержка зависит от приоритета URL, доступности ресурсов, частоты обхода, качества сайта и общей нагрузки на рендеринг. Для одних URL это секунды или минуты, для других — заметная задержка. Это не приговор, но фактор проектирования.

04

Попадание в обработку и индекс

Контент и ссылки, которые уже есть в исходном HTML, могут быть обработаны сразу при краулинге. Контент и ссылки, появляющиеся только после JavaScript, зависят от успешного рендеринга и могут попасть в обработку позже. Если рендеринг даёт ошибку или не завершается корректно, страница может попасть в индекс неполноценно.

01
Краулинг
Бот скачивает raw HTML
сразу
02
Первичная обработка
HTML-контент, href-ссылки и meta могут быть обработаны сразу
после HTML
03
Очередь WRS
JS-страница ставится на рендеринг
позже
04
Rendered HTML
Контент, ссылки и schema после JS обрабатываются позже
сигналы
05
Индексация
Индекс формируется с учётом доступных HTML- и rendered-сигналов

WRS и Яндекс: рендеринг JS на стороне бота

На первом этапе обхода поисковик получает исходный HTML — JS-контент становится доступен только после рендеринга. Рендеринг — отдельный асинхронный этап, который не гарантирован при каждом обходе.

Google: Web Rendering Service (WRS)

Что такое WRS

WRS (Web Rendering Service) — отдельная служба Google, которая запускает headless Chromium для выполнения JavaScript. Это не тот же процесс, что краулинг. Googlebot скачивает HTML и уходит — а страница ставится в очередь WRS на рендеринг.

Суть позиции Google
  • Рендеринг JavaScript — ресурсоёмкий этап, поэтому он выполняется отдельно от первичного краулинга.
  • Между загрузкой HTML и рендерингом может быть задержка.
  • Контент, который появляется только после JavaScript, зависит от успешного выполнения WRS.
  • Поэтому SEO-критичные элементы безопаснее отдавать в исходном HTML.
Что это означает на практике: когда Googlebot обошёл CSR-страницу, он сначала получает исходный HTML. Если там только пустой каркас, а основной текст, ссылки и мета-сигналы появляются после JS, эти элементы зависят от WRS. Пока рендеринг не выполнен успешно, поисковая система работает с неполной версией страницы.

Яндекс: рендеринг на стороне робота

Как устроен рендеринг у Яндекса

Яндексбот умеет выполнять JS на своей стороне. Система автоматически определяет, даёт ли рендеринг новый ценный контент — и если да, включает его для страницы. Владелец сайта может управлять этим вручную через Вебмастер: «рендерить», «не рендерить» или «на усмотрение робота» (по умолчанию).

Что важно знать
  • Рендеринг создаёт дополнительную нагрузку на сервер — робот запрашивает все скрипты и ресурсы страницы.
  • Если JS- и CSS-файлы закрыты через robots.txt, рендеринг не будет работать корректно. Нужно проверить правила Disallow/Allow и убедиться, что роботы могут загружать ресурсы, необходимые для отображения страницы.
  • Если SSR или prerender уже внедрены и сайт отдаёт готовый HTML, рендеринг на стороне робота обычно избыточен: он создаёт дополнительную нагрузку и не решает новую SEO-задачу.

Сравнение: что реально видит бот в момент краулинга

Что бот получает при первом визите
  • Raw HTML с сервера — это всё, что он берёт в момент краулинга
  • JS-файлы и связанные ресурсы могут быть загружены и использованы на этапе рендеринга
  • Контент внутри React/Vue/Angular при чистом CSR может отсутствовать в исходном HTML и становиться доступным только после рендеринга
Что происходит дальше — по очереди
  • URL с высоким приоритетом и доступными ресурсами рендерятся быстрее
  • URL с низким приоритетом, редким обходом или тяжёлыми ресурсами могут ждать заметно дольше
  • Ошибки JS, заблокированные скрипты и таймауты дают неполный rendered HTML
  • Чем больше SEO-критичных элементов зависит от JS, тем выше риск задержки индексации
Практический вывод: всё, что важно для SEO (title, description, canonical, h1, основной текст, внутренние ссылки) — должно быть в исходном HTML с сервера. Не надеяться на рендеринг. SSR или SSG для SEO-критичных блоков — это страховка, а не «перфекционизм».
Коротко: если основной контент страницы есть только в rendered DOM, но отсутствует в raw HTML, индексация зависит от успешного выполнения JavaScript поисковым ботом. Для SEO-критичных страниц безопаснее отдавать основной контент, ссылки и мета-теги в исходном HTML.

CSR, SSR, SSG, ISR — какой режим когда выбирать

Режим рендеринга SEO-характеристика и когда применять
CSR (Client-Side Rendering) При плохой реализации бот может получить почти пустой HTML-каркас до рендеринга. Риск задержки обработки SEO-контента. Подходит для авторизованных зон — где SEO не нужен.
SSR (Server-Side Rendering) Бот сразу получает полный HTML. Оптимально для SEO. Нужен для e-commerce, новостей, лендингов с высоким трафиком.
SSG (Static Site Generation) Статический HTML, нет задержки. Идеально для редко меняющегося контента: блог, FAQ, о компании.
ISR (Incremental Static Regen) Статика с обновлением по расписанию. Хороший компромисс для контента средней динамики: категории, карточки.
Не всё нужно переводить на SSR. Там, где SEO-контент стабилен и редко меняется, SSG или ISR часто достаточно — и дешевле в обслуживании.
Типичный проблемный CSR — бот видит
<html>
<head>
  <title></title>  ← пусто
</head>
<body>
  <div id="root"></div>  ← пусто
  <script src="bundle.js"></script>
</body>
</html>
SSR — бот видит
<html>
<head>
  <title>Каталог диванов</title>
  <meta name="description" content="...">
  <link rel="canonical" href="...">
</head>
<body>
  <h1>Каталог диванов</h1>
  <p>Текст страницы доступен сразу...</p>
  <a href="/product/1/">Диван Monaco</a>
</body>
</html>

Топ-5 паттернов, которые ломают JS SEO

01

Контент за lazy load

Google не взаимодействует со страницей как пользователь: не кликает по кнопкам и не выполняет сценарий просмотра. Поэтому SEO-критичный контент не должен зависеть от ручного скролла, клика или пользовательского события. Частая жертва: отзывы, рекомендации, связанные статьи. Решение: контент должен загружаться, когда становится видимым во viewport, и проверяться через URL Inspection.

02

Контент в табах и аккордеонах

Если контент подгружается только после клика и до взаимодействия отсутствует в DOM, бот может его не увидеть. Если контент уже есть в DOM и скрыт CSS-ом, риск ниже.

03

Ссылки, генерируемые JavaScript

Если навигационные или контентные ссылки рендерятся после загрузки JS — граф ссылок может быть неполным. Критично для меню, перелинковки, infinite scroll.

04

Мета-теги, устанавливаемые на клиенте

Title, description, canonical, hreflang — если они устанавливаются через JS после загрузки страницы, есть риск задержки. SSR-рендеринг этих данных резко снижает риск, потому что бот получает SEO-сигналы уже в исходном HTML.

05

SPA-навигация без серверных URL

Если переходы между «страницами» в SPA не создают уникальные URL на сервере — бот видит только главную. Решение: правильный History API, серверный роутинг, уникальные URL для каждого состояния.

Симптомы JavaScript SEO-проблемы

Признаки JS SEO-проблемы: страница есть в индексе, но не ранжируется; в GSC «просмотренная страница» отличается от браузера; краулер не находит внутренние ссылки; title или canonical отличаются между View Source и rendered DOM; новые страницы долго не попадают в индекс; мобильная версия содержит меньше текста, чем desktop.

Коротко: проблема обычно проявляется не как «сайт не работает», а как расхождение между тем, что видит пользователь, и тем, что доступно поисковому боту в raw HTML, rendered HTML и bot view.

Таблица диагностики: симптом → причина → что проверить

Симптом Вероятная причина Что проверить
Страница проиндексирована, но без основного текста Контент рендерится через CSR, WRS не выполнил рендеринг GSC → URL Inspection → View Crawled Page
Внутренние ссылки не найдены краулером Ссылки генерируются JavaScript после загрузки Screaming Frog: JS-режим vs обычный — сравнить граф ссылок
Title/description некорректные в выдаче Meta устанавливается через JS после загрузки страницы DevTools → View Source (исходный HTML) vs Elements (после JS)
Новые страницы долго не появляются в индексе Низкий приоритет WRS или проблемы с crawl budget Логи + GSC Coverage: crawled vs indexed динамика
Страница выглядит нормально, но ранжируется слабо CSR или lazy load для SEO-критичного контента Сравните три версии: raw HTML / rendered HTML / GSC bot view

Три шага диагностики + код для консоли

Простой тест для проверки расхождения между ботом и пользователем. Никакого специального инструмента не нужно:

Три версии одной страницы

Шаг 1 — Raw HTML

View Source в браузере (Ctrl+U). Что отдаёт сервер без рендеринга. Это то, что бот получает в первый момент. Ищите: есть ли h1, title, основной текст, ссылки — или только пустой каркас с <div id="root">.

Шаг 2 — Rendered HTML

DevTools → Elements (F12). Что видит браузер после полного рендеринга. Это то, что видит пользователь. Сравните с Шагом 1: сколько текста добавил JS?

Шаг 3 — Bot view

GSC URL Inspection → «Просмотреть как Googlebot» → «Просмотреть отображённую страницу». Что видел Googlebot при последнем обходе. Это окончательный ответ о состоянии индексации.

Если между тремя версиями есть расхождения в тексте, ссылках или мета-тегах — это ваш список проблем.

Код диагностики рендеринга для консоли браузера

Запускать в DevTools (F12 → Console). Показывает состояние страницы в текущем режиме — отдельно для ПК и мобильной версии. Для сравнения: откройте DevTools → Toggle Device Toolbar (Ctrl+Shift+M), переключитесь на мобильный UA, перезагрузите страницу и запустите снова. Разница в количестве текста, ссылок, H2 — это и есть проблема мобильного индекса.

Как читать результат

На что смотреть в первую очередь
  • Текст на странице < 500 символов — признак тонкой или неполной rendered DOM-версии. Отдельно проверьте View Source и GSC URL Inspection.
  • CSR-маркеры обнаружены (#root/#app) — нужна проверка View Source
  • Разница текста ПК vs мобильный — возможный mobile-first проблема
  • Время загрузки DOM > 3000 мс — повод проверить тяжёлую гидратацию, JS-ошибки и сетевые задержки.
Зелёные сигналы
  • H1 присутствует, title заполнен
  • Canonical совпадает с текущим URL
  • JSON-LD найден и определён тип
  • Viewport meta присутствует
  • CSR-маркеры не обнаружены
Рендер-диагностика: ПК vs Мобильная — вставить в Console (F12)
(function(){
  const W = window, D = document;
  const vw = W.innerWidth, vh = W.innerHeight;
  const ua = navigator.userAgent;
  const isMobile = /Mobile|Android|iPhone|iPad/i.test(ua);

  // ── Мета-теги ────────────────────────────────────────────────────────────
  const h1 = D.querySelector('h1');
  const h2s = D.querySelectorAll('h2');
  const title = D.title;
  const desc = D.querySelector('meta[name="description"]');
  const canonical = D.querySelector('link[rel="canonical"]');
  const robots = D.querySelector('meta[name="robots"]');
  const viewport = D.querySelector('meta[name="viewport"]');

  // ── Контент ──────────────────────────────────────────────────────────────
  const allText = D.body ? D.body.innerText.trim().length : 0;
  const lazyImgs = D.querySelectorAll('img[loading="lazy"],img[data-src]').length;
  const allImgs = D.querySelectorAll('img').length;

  // ── JSON-LD ──────────────────────────────────────────────────────────────
  const ldScripts = D.querySelectorAll('script[type="application/ld+json"]');
  const ldTypes = [];
  ldScripts.forEach(s => {
    try {
      const data = JSON.parse(s.textContent);
      const t = data['@type'] || (data['@graph'] && data['@graph'].map(x=>x['@type']).join('+'));
      if(t) ldTypes.push(t);
    } catch(e){}
  });

  // ── Ссылки ───────────────────────────────────────────────────────────────
  const links = D.querySelectorAll('a[href]');
  const internal = Array.from(links).filter(a => a.href.startsWith(location.origin)).length;
  const external = links.length - internal;

  // ── CSR-детекция ─────────────────────────────────────────────────────────
  const csrMark = ['#root','#app','#__nuxt','app-root'].some(m => D.querySelector(m));

  // ── Производительность ───────────────────────────────────────────────────
  let loadMs = 'н/д';
  if(W.performance && W.performance.timing) {
    const t = W.performance.timing;
    loadMs = (t.domComplete - t.navigationStart) + ' мс';
  }

  // ── Медиа-брейкпоинт ────────────────────────────────────────────────────
  const bp = vw <= 480 ? 'small-mobile ≤480px'
           : vw <= 640 ? 'mobile ≤640px'
           : vw <= 768 ? 'tablet ≤768px'
           : 'desktop >768px';

  // ── Вывод ────────────────────────────────────────────────────────────────
  const mode = isMobile ? '📱 МОБИЛЬНЫЙ' : '🖥  ПК';
  console.group(`%c🔍 РЕНДЕР [${mode}] [${vw}×${vh}px] ${bp}`,
    'font-size:13px;font-weight:bold;color:#2dd4a8');

  console.group('📄 Мета');
  console.log('Title:', `"${title}"`, `(${title.length} симв.)`);
  console.log('H1:', h1 ? `"${h1.innerText.trim().slice(0,80)}"` : '❌ НЕТ H1');
  console.log('H2:', h2s.length, '|', Array.from(h2s).map(h=>h.innerText.trim().slice(0,40)));
  console.log('Description:', desc ? desc.content.slice(0,100) : '❌ НЕТ');
  console.log('Canonical:', canonical ? canonical.href : '❌ НЕТ');
  console.log('Robots:', robots ? robots.content : '(не задан)');
  console.log('Viewport:', viewport ? '✅ ' + viewport.content : '❌ НЕТ — проблема на мобильных');
  console.groupEnd();

  console.group('🖼 Контент');
  console.log('Текст:', allText + ' симв.' + (allText < 500 ? ' ⚠️ МАЛО — проверьте View Source и GSC URL Inspection' : ' ✅'));
  console.log('Изображений:', allImgs, '| lazy:', lazyImgs);
  console.groupEnd();

  console.group('🔗 Ссылки');
  console.log('Внутренние:', internal, '| Внешние:', external);
  console.groupEnd();

  console.group('🤖 JS / CSR');
  console.log('CSR-маркеры:', csrMark ? '⚠️ ОБНАРУЖЕНЫ (#root/#app) — проверьте View Source' : '✅ не найдены');
  console.log('Время загрузки DOM:', loadMs);
  console.groupEnd();

  console.group('📊 Schema.org JSON-LD');
  console.log(ldScripts.length + ' блоков | Типы:', ldTypes.join(', ') || '❌ нет');
  console.groupEnd();

  console.group('📐 Адаптивность');
  console.log('Брейкпоинт:', bp);
  console.log('%c💡 Для сравнения ПК vs Мобильный:', 'color:#ffd166');
  console.log('%c   F12 → Ctrl+Shift+M → выберите iPhone/Pixel → перезагрузите → запустите снова', 'color:#ffd166');
  console.log('%c   Сравните: текст, ссылки, H2 — разница = проблема мобильного индекса', 'color:#ffd166');
  console.groupEnd();

  console.groupEnd();
})();
Анонимизированный кейс из технического аудита: интернет-магазин электроники, категории на CSR (React SPA). GSC показывал: 40% категорий проиндексированы пустыми — без текста, без h1. Органика не росла 8 месяцев при активной работе с контентом. Проблема не в контенте — бот не видел его вовсе. Перевели страницы категорий на SSR, карточки — на ISR. Через 6 недель 95% категорий проиндексированы полноценно. Трафик на категории вырос на 60% за квартал. Ниша и цифры сохранены в рабочем диапазоне, бренд не раскрывается из-за NDA.

Как поисковики находят страницы на JavaScript-сайтах

Краулер не строит маршрут как пользователь. Он не обязан нажимать кнопки, раскрывать фильтры, выбирать вкладки и дожидаться пользовательских сценариев. Для discovery нужны настоящие ссылки: <a href="...">.

Это базовый механизм обнаружения страниц — и он не изменился из-за JavaScript. Изменилось то, что JS-фреймворки создают иллюзию навигации без фактической смены URL и HTML-ссылок. Для пользователя выглядит одинаково, для бота — принципиально по-разному.

Почему бот не «кликает» сайт как пользователь

Что делает бот при обходе

Googlebot скачивает HTML, извлекает все ссылки с атрибутом href и добавляет их в очередь обхода. Он не симулирует взаимодействие: кнопки «показать ещё», слайдеры, табы, выпадающие фильтры — всё это вне его маршрута. Если контент за этими элементами не продублирован HTML-ссылками или отдельными URL, бот может его не обнаружить.

Что теряется без <a href>
  • Кнопки с onclick без href — бот игнорирует
  • Навигация через window.location без HTML-ссылки — не обнаруживается
  • Ссылки в data-url или data-href атрибутах — не признаются ссылками
  • Контент, подгружаемый через API только после клика, — недоступен без пользовательского события, если нет отдельного URL, HTML-ссылки или запасного DOM-маршрута

Какие ссылки считаются crawlable

Принцип

Ссылка crawlable только если она оформлена как <a href="/path/"> — с настоящим URL в атрибуте. Важные страницы нельзя прятать за JavaScript-событиями. Это касается не только навигации, но и любых SEO-значимых переходов: пагинация, архивы, категории, статьи.

Практика
  • Пагинация (pagination): каждая страница должна иметь URL и быть доступна через <a href>
  • Листинги и архивы: нельзя делать навигацию только через JS-фильтрацию без URL
  • Блоговые ленты: ссылка на каждую статью в href, не только в data- атрибуте
  • Внутренняя перелинковка: если ссылка появляется через JS после взаимодействия — бот её не видит

Infinite scroll и load more без потери индексации

Проблема

Infinite scroll сам по себе не ломает SEO. Проблема — отсутствие URL-доступа к контенту внутри него. Если блоки с контентом не имеют собственных URL или ссылок на них в HTML, бот может не дойти до скрытых элементов даже после рендеринга. Load more через JS — та же история: кнопка без href не помогает краулеру.

Решение
  • Pagination как запасной маршрут: даже при infinite scroll — HTML-ссылки на страницы ?page=2, ?page=3
  • Каждый значимый блок или группа контента должны иметь доступный URL и HTML-ссылку. Infinite scroll может быть основным UX-сценарием, но для краулера должен существовать запасной маршрут через пагинацию или обычные ссылки.
  • Sitemap не заменяет нормальную перелинковку через <a href> — это дополнение, не замена
  • Проверка: откройте страницу без JS (отключить в DevTools) — видны ли ссылки на весь контент?
Практический вывод: если страница существует только как JS-состояние без URL — для поисковика её не существует. Правило для разработки: каждый уникальный контентный блок, который должен ранжироваться, должен быть доступен по уникальному URL через обычную HTML-ссылку.

SPA и SEO — где ломаются URL, индексация и рост

Single Page Application создаёт одну из самых системных проблем в JS SEO: видимость для пользователя не совпадает с видимостью для бота. Приложение выглядит как набор страниц, а для краулера это один документ с меняющимся состоянием.

Это не приговор для SPA — это архитектурная задача. Решается она на уровне проектирования URL, серверного роутинга и обработки ошибок. Если эти три вещи не продуманы — SPA-сайт теряет в индексации системно.

History API против hash routing

История вопроса

Ранние SPA использовали hash routing — URL вида /#tab1, /#product/42. Фрагмент #fragment после решётки обрабатывается браузером и не передаётся серверу. Для бота это один и тот же URL — разные хеши не создают разных страниц. Два состояния /#tab1 и /#tab2 — это не два документа, это один документ.

History API как правильный путь

History API позволяет менять URL в браузере без перезагрузки страницы, используя настоящие пути: /catalog/chairs/, /product/42/. Это реальные URL, которые бот может краулить независимо. Требование — серверный роутинг: сервер должен уметь отвечать на каждый из этих путей, а не только на корень. Иначе — 404 при прямом переходе.

Почему soft 404 — типичная проблема JavaScript-приложений

Что такое soft 404

Soft 404 — это страница, которая отдаёт HTTP 200, но содержит «не найдено» или пустой контент. Многие SPA возвращают 200 OK на любой несуществующий маршрут — браузер рендерит клиентскую заглушку «Страница не найдена», а сервер при этом отвечает кодом 200. По HTTP это не ошибка, потому что сервер отдаёт 200 OK. Но поисковик может распознать такую страницу как soft 404 или начать тратить обход на пустые URL.

Последствия и решение
  • Soft 404 тратит crawl budget — бот обходит «пустышки» вместо реального контента
  • Может привести к индексации страниц-заглушек в выдаче
  • Правильный ответ для несуществующих маршрутов: HTTP 404 или 410
  • Серверный роутинг должен возвращать 404/410 для несуществующих путей — не перекладывать это на клиент
  • Проверка: откройте несуществующий URL, проверьте Network → Status Code в DevTools

Параметры URL и дубли в JS-приложениях

Откуда берутся дубли

JS-приложения часто генерируют URL-параметры для хранения состояния: активная вкладка, открытый фильтр, выбранный размер, session ID, UTM-метки. Один и тот же экран может существовать в десятках URL-вариаций. Для поисковика это дубли или почти-дубли — они конкурируют друг с другом и размывают сигналы.

Принципы управления URL-состоянием
  • Параметры, влияющие на контент, — нужны URL; параметры UI-состояния — лучше в localStorage или sessionStorage, не в URL
  • Canonical должен указывать на каноническую версию без лишних параметров
  • Временные и пользовательские параметры лучше обрабатывать через rel=canonical, meta robots noindex / X-Robots-Tag или не отдавать как индексируемые URL
  • URL-архитектура в JS-сайте — часть SEO-архитектуры, не фронтенд-деталь
Ключевое правило SPA SEO: каждый URL, который должен ранжироваться, должен возвращать корректный HTTP-статус с сервера и содержать нужный контент в исходном HTML или после рендеринга. Клиентский роутинг не заменяет серверный.

Почему meta, canonical и noindex через JavaScript часто работают хуже, чем кажется

Meta-теги, canonical и robots-директивы — это сигналы для поисковика. Проблема в том, что они должны быть доступны в момент краулинга, а не появляться через несколько секунд после выполнения клиентского JS. В разрыве между «HTML с сервера» и «HTML после рендеринга» живут типичные SEO-баги.

Что опасно генерировать только на клиенте

Элементы, критичные для SEO
  • <title> — должен присутствовать в исходном HTML
  • <meta name="description"> — то же
  • <link rel="canonical"> — поисковик смотрит его при краулинге
  • <meta name="robots"> — если появляется позже рендеринга, поведение непредсказуемо
  • hreflang — особенно чувствителен к задержкам рендеринга
Почему клиентская генерация рискованна

Если эти элементы появляются только после JS — бот может увидеть их с задержкой или в неполном виде при первом краулинге. После WRS-рендеринга картина может исправиться, но до этого возникает временное расхождение между raw HTML и rendered HTML. Чем больше SEO-сигналов зависит от JS, тем выше риск задержек и неполной обработки. SSR этих элементов резко снижает неопределённость, потому что бот получает SEO-сигналы уже в исходном HTML.

Ловушка noindex

Сценарий-ловушка

Встречается в SPA: в исходном HTML прописан noindex как дефолт для всех страниц, а JS после загрузки убирает его для «нужных» страниц. Логика: всегда закрыто, JS открывает. Проблема: если Googlebot видит noindex в исходном HTML, он может не выполнять рендеринг страницы и не дождаться клиентского JavaScript, который убирает эту директиву.

Правильные альтернативы
  • SSR: noindex и его отсутствие проставляются на сервере для каждого маршрута отдельно
  • X-Robots-Tag в HTTP-ответе — серверная альтернатива мета-тегу; работает независимо от JS
  • Схема «noindex в HTML, JS убирает» — ненадёжна; лучше решать на уровне сервера
  • Для страниц, которые никогда не должны индексироваться, — X-Robots-Tag: noindex на сервере надёжнее

Canonical в JS — не место для хаоса

Типичная проблема

Canonical через JavaScript технически может быть обработан, но это слабее, чем canonical в исходном HTML. Опасность не в самом JS, а в конфликте: один canonical в raw HTML, другой после рендеринга. В JS-приложениях такое нередко возникает из-за нескольких слоёв: HTML-шаблон, React Helmet, vue-meta, Next.js Head или CMS-логика.

Принцип
  • Один canonical на страницу — без дублирования и конфликта
  • SPA-маршруты и каноникализация проектируются вместе, не по отдельности
  • Canonical не должен конфликтовать между исходным HTML и JS-версией
  • Проверка: View Source vs DevTools Elements — canonical одинаковый?
Практический вывод: canonical лучше отдавать в исходном HTML и не менять его после рендеринга. Если canonical добавляется через JavaScript, проверьте, что в raw HTML и rendered DOM нет разных или дублирующих canonical-значений. Для noindex и robots-директив безопаснее серверный HTML или HTTP-заголовок X-Robots-Tag.

Structured data и JavaScript

Структурированная разметка — один из тех элементов, где вопрос «можно ли через JS» чаще всего задают, и чаще всего неправильно понимают ответ. Технически можно. Практически — с оговорками, которые важны именно в контексте JS SEO.

Можно ли генерировать schema через JS

Технически

Да, Google умеет обрабатывать structured data, добавленную через JavaScript, — это подтверждено документацией. JSON-LD, инжектированный через JS после загрузки страницы, теоретически будет обработан после рендеринга. Разница между кастомной JS-инъекцией и Google Tag Manager минимальна — в обоих случаях разметка появляется после WRS-рендеринга.

На практике
  • Обработка зависит от успешного рендеринга — если WRS не дошёл до страницы, разметки нет
  • Через Google Tag Manager structured data добавлять технически можно, но GTM добавляет ещё один слой зависимости и задержки: разметка появляется не в исходном HTML, а после выполнения дополнительного клиентского кода
  • Чем динамичнее данные (цены, наличие, рейтинги), тем выше риск нестабильной обработки через клиентский JS

Где это особенно опасно

E-commerce и динамические данные

Product-разметка с ценой, наличием, вариациями товара — если всё это приходит из клиентского JS, обработка менее предсказуема, чем при SSR. Цена может измениться между краулингом и рендерингом. Наличие может устареть. Для rich results (карточки товаров в выдаче) нестабильная разметка = нестабильный внешний вид в SERP.

Что под угрозой
  • Product-схема с динамическими полями (цена, наличие)
  • FAQ-схема, генерируемая из CMS через JS
  • Review/Rating, если данные подгружаются асинхронно
  • Article-схема без author, datePublished, dateModified, headline и image

Практическое правило

Приоритет

Критичная structured data должна по возможности приходить с сервера — в теге <script type="application/ld+json"> внутри исходного HTML. JSON-LD в исходном HTML — наиболее надёжный вариант для SEO-критичной structured data, потому что разметка доступна без ожидания клиентского JavaScript.

Когда JS-разметка допустима
  • Если данные слишком динамичны для SSR — JS допустим, но нужна проверка
  • После внедрения: Rich Results Test на конкретных URL
  • Проверить rendered HTML в GSC URL Inspection — разметка там присутствует?
  • Google Tag Manager для structured data — скорее исключение, чем правило
Практический вывод: если structured data критична для внешнего вида в SERP (rich results: товары, FAQ, рецепты, статьи), её лучше рендерить на сервере и отдавать в исходном HTML. JS-разметка допустима как дополнение, но не как основной механизм для SEO-значимых типов схем.

JavaScript SEO в Яндексе

Большинство материалов по JS SEO написаны через призму Google: WRS, Googlebot, GSC. Это понятно — но для русскоязычной аудитории Яндекс нередко приоритетнее. Механизмы обработки JS у двух систем совпадают не полностью.

Почему нельзя писать о JS SEO только через Google

Ключевые отличия

У Яндекса другая модель управления JS-рендерингом: в Вебмастере можно указать, должен ли робот выполнять JavaScript при обходе страниц. По умолчанию робот сам определяет, нужен ли рендеринг для конкретных страниц. Выполнение JS создаёт дополнительную нагрузку, поэтому этот режим нужно настраивать осознанно.

Общий знаменатель
  • Оба поисковика предпочитают получать критичный контент в исходном HTML
  • Оба умеют обрабатывать JS, но с задержками и ограничениями
  • Оба чувствительны к ошибкам JS и заблокированным ресурсам
  • SSR или SSG — надёжнее клиентского рендеринга в обоих случаях

Что есть в Яндекс Вебмастере для JS SEO

Инструменты диагностики

В Yandex Webmaster есть раздел «Проверка страницы» (аналог GSC URL Inspection). Там можно проверить, как Яндексбот видит конкретную страницу. Важная функция — JavaScript page rendering: можно сравнить отображение страницы с исполнением JS и без него. Это прямой способ увидеть расхождение между исходным HTML и рендеренной версией в понимании Яндекса.

Настройки рендеринга
  • «Рендерить» — Яндексбот будет выполнять JS для страниц сайта
  • «Не рендерить» — бот работает с исходным HTML
  • «На усмотрение робота» — по умолчанию; автоматическое определение
  • Выбор типа устройства при проверке — полезно для mobile-first диагностики

Важный нюанс по SSR и prerender для Яндекса

Рекомендация Яндекса

Если SSR или prerender уже внедрены и сайт отдаёт готовый HTML с сервера, рендеринг JavaScript на стороне робота Яндекса обычно избыточен. Его можно отключить в Вебмастере или настроить точечно только для страниц, где он действительно нужен.

Практические следствия
  • SSR + «рендерить в Вебмастере» — избыточно и нагружает сервер при краулинге
  • При переходе на SSR/prerender стоит пересмотреть настройки рендеринга в Yandex Webmaster
  • Ошибки JS, заблокированные скрипты через robots.txt — мешают рендерингу так же, как у Google
  • Allow-директивы для JS-файлов в robots.txt нужны обоим роботам
Вывод: для русскоязычных сайтов — проверяйте JS SEO в обоих инструментах. Yandex Webmaster и GSC дают разные срезы одной проблемы. Расхождение между ними часто указывает на специфику обработки конкретного JS-паттерна тем или иным ботом.

Dynamic rendering — не стратегия, а обходной путь

Когда в проекте нет возможности быстро перейти на SSR, а JS-контент нужно сделать доступным для ботов, нередко предлагают dynamic rendering. Это решение работает, но важно понимать его статус и ограничения.

Что такое dynamic rendering

Принцип работы

Dynamic rendering — это когда ботам и пользователям отдаётся разная версия страницы. Пользователь получает клиентское JS-приложение (CSR). Боту — предварительно рендеренный статический HTML, подготовленный сервером (через Puppeteer, Rendertron или аналоги). Определение бота происходит по User-Agent.

Типичная реализация
  • Nginx или middleware проверяет User-Agent
  • Боты (Googlebot, Yandexbot и другие) → получают HTML от prerender-сервиса
  • Пользователи → получают оригинальный JS-бандл
  • Prerender может быть self-hosted или облачным (Prerender.io и аналоги)

Почему это не лучший путь

Позиция Google

Google в документации описывает dynamic rendering именно как workaround — временный обходной путь для сайтов, которые не могут перейти на SSR. Это не рекомендованный долгосрочный подход. Предпочтительные решения в порядке приоритета: SSR, static rendering, ISR, hydration.

Проблемы на практике
  • Расхождение между тем, что видит пользователь, и тем, что видит бот — потенциально граничит с клоакингом при значительных различиях
  • Prerender-инфраструктура требует поддержки и обновлений
  • Кеш пререндера может устаревать — бот получает старый HTML
  • Дополнительная нагрузка на инфраструктуру
Коротко: dynamic rendering — не архитектура по умолчанию, а временный обходной путь для старых CSR-систем, где быстрый переход на SSR, SSG, ISR или гибридную гидратацию невозможен.

Когда dynamic rendering оправдан

Подходящие сценарии

Основной случай — когда переход на SSR невозможен в обозримой перспективе (легаси-код, ограничения технологического стека, бюджет), а JS-контент критичен для индексации. В этом случае dynamic rendering как временная мера лучше, чем ничего. Также подходит для частей сайта, где SSR технически сложен, но SEO-важен.

Ограничения сценария
  • Не замена нормальной архитектуре — только временная мера
  • Требует мониторинга актуальности кеша
  • Контент для бота и пользователя должен быть максимально близким — значительные отличия недопустимы
  • Если выбираете dynamic rendering: зафиксируйте как технический долг с планом перехода
Практический вывод: dynamic rendering решает проблему JS-индексации как обходной путь. Для проектов на старте или при наличии ресурсов — инвестируйте сразу в SSR или SSG. Dynamic rendering как временная мера — приемлемо. Как долгосрочная стратегия — нет.

Что бот не видит или видит иначе, даже если JavaScript формально исполняется

Успешный рендеринг — не гарантия полноценной индексации. Даже когда WRS выполнил JS и бот получил рендеренный HTML, часть контента может отсутствовать или отличаться. Причины — в том, как JavaScript-приложения используют контекст пользователя, который у бота просто нет.

Контент, завязанный на cookies, storage и user state

Проблема

Не стоит рассчитывать, что бот придёт с нужными cookies, localStorage, sessionStorage, авторизацией, регионом или историей действий пользователя. SEO-критичный контент не должен зависеть от такого состояния.

Типичные случаи
  • Персонализированный контент («Вы смотрели», «Рекомендуем») — бот видит пустые блоки
  • Контент за paywall с cookies-проверкой — бот может не пройти через JS-логику проверки
  • Состояние форм или онбординга из localStorage — бот не имеет этих данных
  • Региональный контент из sessionStorage — бот получит дефолт без региона
  • Настройки пользователя (язык, тема, фильтры), сохранённые в cookies — не восстанавливаются

Permission API и сценарии, требующие действий пользователя

Что не воспроизводится

Разрешения браузера (геолокация, камера, микрофон, уведомления), интерактивные события, контент за модальными окнами, всплывающие формы, контент, который появляется после hover — всё это не воспроизводится ботом. Если важный SEO-контент доступен только через эти сценарии — он не попадёт в индекс.

Принцип
  • Важный контент нельзя делать зависимым от действия пользователя (клик, hover, разрешение)
  • Модальные окна с SEO-контентом — плохая идея: бот видит содержимое DOM, но не взаимодействует
  • Контент, показываемый после клика («читать дальше», «развернуть») — должен присутствовать в DOM, пусть и CSS-скрытый

JS errors и ресурсы, которые не загрузились

Скрытые источники проблем

Рендеринг может «пройти» с точки зрения WRS, но дать неполный результат из-за: ошибки в JS (unhandled exception, которая прерывает выполнение), заблокированного внешнего ресурса (CDN недоступен, скрипт из robots.txt закрыт), таймаута (скрипт выполняется дольше, чем отведено на рендеринг). В любом из этих случаев бот получит частичный HTML без части контента.

Как диагностировать
  • GSC URL Inspection → «Просмотреть отображённую страницу» — сравнить с ожидаемым
  • DevTools Network: проверить, все ли ресурсы загрузились (нет ли 4xx на JS-файлы)
  • DevTools Console: нет ли ошибок JS при загрузке страницы
  • robots.txt: JS-файлы и внешние скрипты не должны быть закрыты для ботов
  • Проверка с отключёнными сторонними скриптами: основной контент должен работать без них
Итог раздела: «JS исполняется» — не равно «бот видит то, что нужно». Контекст пользователя (cookies, localStorage, sessionStorage, история действий) у бота нет. Разрешения и интерактивные сценарии — недоступны. Ошибки и таймауты JS — дают частичный результат. Единственный способ убедиться — проверка через GSC URL Inspection + анализ Console и Network в DevTools с имитацией «чистого» браузера.

Что безопаснее отдавать в исходном HTML

Rendering budget: почему JS-зависимость масштабируется в потери

Rendering budget — не отдельный отчёт в GSC и не публичная метрика Google. Это практическое обозначение ограниченного ресурса, который поисковик тратит на выполнение JavaScript.

Почему это важнее, чем кажется

Рендеринг — самый ресурсоёмкий этап обработки страницы. Crawl budget и rendering budget — разные ресурсы, но оба конечны. На сайте с тысячами URL JS-зависимость масштабируется в системные потери: часть страниц может рендериться с задержкой, а часть — обрабатываться неполноценно из-за ошибок, таймаутов или недоступных ресурсов. Это не проблема одной страницы — это задержка индексации целого раздела.

Практический вывод
  • Каждая JS-зависимая страница дороже в обработке, чем страница с готовым HTML
  • Скорость обработки JS-зависимых страниц зависит от приоритета URL, доступности ресурсов, частоты обхода, технического состояния сайта и общей нагрузки на рендеринг
  • Перевод SEO-критичных шаблонов на SSR/SSG снижает давление на rendering budget
  • Для крупных сайтов это не «перфекционизм» — это управление индексацией на масштабе

Для SEO-критичных шаблонов эти элементы лучше отдавать в исходном HTML или через SSR/SSG/ISR. Технически часть сигналов может появляться после JavaScript, но чем важнее страница для органики, тем меньше она должна зависеть от клиентского рендеринга.

Безопасный состав исходного HTML для SEO-критичных страниц
  • <title> — главный сигнал для поисковика и отображения в SERP
  • <meta name="description"> — используется в сниппете
  • <link rel="canonical"> — управляет каноникализацией; конфликт между HTML и JS-версией ломает сигнал
  • <meta name="robots"> или X-Robots-Tag в HTTP-ответе — директивы индексирования должны быть на сервере, а не на клиенте
  • <h1> — один, в исходном HTML, не через JS
  • Основной текст страницы — всё, что должно ранжироваться
  • Внутренние ссылки через <a href> — для discovery и распределения PageRank
  • JSON-LD разметка — особенно для критичных типов: Product, Article, FAQPage
  • Корректный HTTP-статус — 404/410 для несуществующих URL, не 200 OK
Если хотя бы один из этих элементов появляется только после JS — это риск. Проверяется за 30 секунд: Ctrl+U (View Source) и ищете каждый пункт списка. Что отсутствует в View Source — то бот может не увидеть.

Короткий вывод для разработки

  1. SEO-критичный контент должен быть в исходном HTML или гарантированно доступен после серверного/статического рендеринга.
  2. Все индексируемые состояния должны иметь уникальный URL и корректный HTTP-статус.
  3. Внутренние ссылки должны быть обычными <a href>.
  4. Canonical, robots, title, description и H1 не должны зависеть только от клиентского JS.
  5. Проверка внедрения — сравнение raw HTML, rendered DOM и bot view.

Приоритетный план исправлений

01

Диагностика трёх версий HTML

По репрезентативной выборке шаблонов. Не смотреть случайные страницы — смотреть коммерческие шаблоны с наибольшим потенциалом.

02

Составить список расхождений

Проблемные шаблоны с описанием симптомов. Это готовое техзадание для разработки.

03

Выбрать 1–2 SEO-критичных шаблона

С наибольшим потенциалом. Не чинить всё подряд — начать с «денежных» страниц.

04

Исправить критичный контент

Мета-теги, h1, основной текст, навигационные ссылки. Эти элементы нужны ботам в исходном HTML.

05

Решить вопрос с lazy load

На SEO-важных блоках. Либо убрать lazy load, либо обеспечить присутствие контента в DOM при инициальном рендеринге.

06

Оценить переход на SSR/ISR

Для приоритетных шаблонов. SSR нужен не везде — только там, где есть критичный для ранжирования контент, недоступный ботам.

FAQ по JavaScript SEO

Что такое JavaScript SEO?

JavaScript SEO — это контроль того, чтобы контент, ссылки, мета-теги, canonical, robots-директивы и structured data, которые создаёт или меняет JavaScript, были доступны поисковым системам для обхода, рендеринга и индексации. Главная задача — не запретить JS, а сделать SEO-критичные сигналы видимыми для бота.

Вредит ли JavaScript SEO?

JavaScript сам по себе не вредит SEO. Проблемы появляются, когда основной текст, H1, внутренние ссылки, canonical, noindex или structured data становятся доступны только после клиентского рендеринга. Если бот не выполнит JS, выполнит его с ошибкой или увидит неполную DOM-версию, страница может индексироваться хуже.

Может ли Google индексировать JavaScript?

Google может выполнять JavaScript через Web Rendering Service, но рендеринг идёт отдельным этапом после краулинга и требует ресурсов. Поэтому безопаснее проектировать SEO-важные страницы так, чтобы основной контент, ссылки и мета-теги уже были в исходном HTML, а JavaScript не был единственным источником SEO-сигналов.

Почему React-сайт может плохо индексироваться?

React-сайт плохо индексируется не из-за React как технологии, а из-за CSR-паттернов: пустой #root в исходном HTML, ссылки без href, контент после lazy load, SPA-маршруты без серверных URL, meta/canonical через клиентский JS. Для SEO-критичных шаблонов обычно нужны SSR, SSG, ISR или гибридная гидратация.

Что лучше для SEO: CSR, SSR, SSG или ISR?

Для публичных SEO-страниц надёжнее SSR, SSG или ISR, потому что бот сразу получает готовый HTML. CSR подходит для личных кабинетов, интерфейсов и зон без поискового интента. На практике выбор зависит от типа страницы: блог и справка — SSG, категории и карточки — SSR/ISR, закрытые интерфейсы — CSR.

Как проверить, видит ли Googlebot контент?

Сравните три версии: View Source как raw HTML, DevTools Elements как rendered HTML и GSC URL Inspection как bot view. Если H1, основной текст, ссылки, canonical или JSON-LD есть только в rendered DOM, но отсутствуют в raw HTML или bot view, это потенциальная JS SEO-проблема.

Нужно ли использовать dynamic rendering?

Dynamic rendering стоит использовать только как временный обходной путь, когда переход на SSR, SSG или ISR невозможен, а JS-контент критичен для индексации. Для новой архитектуры это плохая стратегия по умолчанию: она усложняет инфраструктуру и требует постоянной проверки соответствия версии для пользователя и версии для бота.

Как Яндекс обрабатывает JavaScript?

Яндекс умеет рендерить JavaScript на стороне робота и даёт настройки рендеринга в Вебмастере. Но для SEO это не замена готовому HTML. Если сайт уже использует SSR или prerender, рендеринг на стороне робота может быть избыточным. Проверять нужно отдельно: GSC показывает один срез, Яндекс.Вебмастер — другой.

Источники и что проверять дальше

Что проверить дальше на своём сайте
  1. Сравнить raw HTML, rendered DOM и GSC URL Inspection для 10–20 типовых URL.
  2. Проверить, есть ли в исходном HTML title, description, canonical, H1, основной текст и внутренние ссылки.
  3. Проверить lazy load, табы, фильтры, пагинацию и load more: есть ли запасной маршрут через URL и <a href>.
  4. Проверить, не меняются ли canonical/noindex между View Source и rendered DOM.
  5. Проверить Яндекс.Вебмастер отдельно: как робот видит страницу с включённым и отключённым JavaScript-рендерингом.

Нужно проверить JS SEO на проекте?

Проверю 10–20 URL: raw HTML, rendered DOM, Googlebot view, мобильную версию, canonical/noindex, внутренние ссылки, schema.org и ошибки рендеринга. На выходе — таблица проблем по URL и шаблонам, приоритеты исправления и готовые формулировки задач для разработчиков.

Написать в Telegram