Проблема не в React и не в Vue — проблема в паттерне: критичный SEO-контент недоступен в исходном HTML и появляется только после JavaScript-рендеринга, который бот может не выполнить.

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

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

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

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

01

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

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

02

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

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

03

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

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

04

Попадание в индекс

Только после рендеринга контент и ссылки попадают в индекс. Если рендеринг даёт ошибку или превышает таймаут — страница не попадёт вообще.

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

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

Google: Web Rendering Service (WRS)

Что такое WRS

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

Официальная позиция Google
  • «Рендеринг — ресурсоёмкая операция, поэтому он происходит в отдельной очереди после краулинга.» — Google Search Central, JavaScript SEO basics
  • «Задержка между краулингом и рендерингом может составлять от нескольких секунд до нескольких недель, в зависимости от приоритета ресурса.» — developers.google.com/search/docs/crawling-indexing/javascript
  • «Контент, который появляется только после выполнения JS, попадает в индекс позже, чем контент из исходного HTML.» — Martin Splitt, Google DevRel
Что это означает на практике: когда Googlebot обошёл вашу CSR-страницу, он увидел пустой каркас. JS-контент уйдёт в очередь WRS. Пока очередь не дойдёт до вашего сайта — страница будет проиндексирована без этого контента или не проиндексирована вовсе.

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

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

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

Что важно знать
  • Рендеринг создаёт дополнительную нагрузку на сервер — робот запрашивает все скрипты и ресурсы страницы.
  • Если скрипты закрыты через robots.txt, рендеринг не будет работать корректно — нужны директивы Allow или Clean-param.
  • Яндекс прямо указывает: рендеринг на стороне робота не даёт преимуществ перед SSR или пререндерингом. Предпочтительный путь — отдавать боту готовый HTML.

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

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

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

Режим рендеринга SEO-характеристика и когда применять
CSR (Client-Side Rendering) Бот видит пустой каркас до рендеринга. Риск задержки. Подходит для авторизованных зон — где SEO не нужен.
SSR (Server-Side Rendering) Бот сразу получает полный HTML. Оптимально для SEO. Нужен для e-commerce, новостей, лендингов с высоким трафиком.
SSG (Static Site Generation) Статический HTML, нет задержки. Идеально для редко меняющегося контента: блог, FAQ, о компании.
ISR (Incremental Static Regen) Статика с обновлением по расписанию. Хороший компромисс для контента средней динамики: категории, карточки.
Не всё нужно переводить на SSR. Там, где SEO-контент стабилен и редко меняется, SSG или ISR часто достаточно — и дешевле в обслуживании.

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

01

Контент за lazy load

Бот не прокручивает страницу. Если важный контент загружается при скролле — бот его не видит. Частая жертва: отзывы, рекомендации, связанные статьи. Решение: контент должен присутствовать в DOM при рендеринге.

02

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

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

03

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

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

04

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

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

05

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

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

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

Симптом Вероятная причина Что проверить
Страница проиндексирована, но без основного текста Контент рендерится через 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 символов — признак CSR без рендеринга в боте
  • CSR-маркеры обнаружены (#root/#app) — нужна проверка View Source
  • Разница текста ПК vs мобильный — возможный mobile-first проблема
  • Время рендеринга > 3000 мс — медленная гидратация, риск таймаута WRS
Зелёные сигналы
  • 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 renderMs = 'н/д';
  if(W.performance && W.performance.timing) {
    const t = W.performance.timing;
    renderMs = (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 ? ' ⚠️ МАЛО — возможно CSR без рендеринга' : ' ✅'));
  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('Время рендеринга:', renderMs);
  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% за квартал. Ни одна новая страница не создавалась.

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

01

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

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

02

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

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

03

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

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

04

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

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

05

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

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

06

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

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

Проверю рендеринг на 10 ваших URL

Raw HTML, rendered HTML, bot view — и покажу, где расхождение. Конкретный список проблем и порядок их устранения, а не абстрактные рекомендации.

Написать в Telegram