Проблема не в React и не в Vue — проблема в паттерне: критичный SEO-контент недоступен в исходном HTML и появляется только после JavaScript-рендеринга, который бот может не выполнить.
Поэтому один сайт на React отлично ранжируется, а другой теряет половину индекса. Технология не виновата. Виноват конкретный способ её использования.
Как Googlebot обрабатывает JavaScript
Googlebot понимает JavaScript. Но не так, как браузер пользователя, и не мгновенно.
Упрощённая модель процесса:
Скачивание исходного HTML
Бот получает HTML от сервера. Это то, что вы видите в View Source.
Очередь рендеринга (WRS)
Страница ставится в очередь Web Rendering Service. Рендеринг происходит позже — срок зависит от приоритета ресурса.
Задержка рендеринга
Для крупных авторитетных сайтов задержка меньше. Для небольших — может составлять несколько дней. Это не приговор, но фактор проектирования.
Попадание в индекс
Только после рендеринга контент и ссылки попадают в индекс. Если рендеринг даёт ошибку или превышает таймаут — страница не попадёт вообще.
WRS и Яндекс: рендеринг JS на стороне бота
Здесь важно понять одно: поисковая система физически не может увидеть JS-контент в момент краулинга. Рендеринг — отдельный этап, который происходит асинхронно и не гарантирован при каждом обходе.
Google: Web Rendering Service (WRS)
WRS (Web Rendering Service) — отдельная служба Google, которая запускает headless Chromium для выполнения JavaScript. Это не тот же процесс, что краулинг. Googlebot скачивает HTML и уходит — а страница ставится в очередь WRS на рендеринг.
- «Рендеринг — ресурсоёмкая операция, поэтому он происходит в отдельной очереди после краулинга.» — Google Search Central, JavaScript SEO basics
- «Задержка между краулингом и рендерингом может составлять от нескольких секунд до нескольких недель, в зависимости от приоритета ресурса.» — developers.google.com/search/docs/crawling-indexing/javascript
- «Контент, который появляется только после выполнения JS, попадает в индекс позже, чем контент из исходного HTML.» — Martin Splitt, Google DevRel
Яндекс: рендеринг на стороне робота
Яндексбот умеет выполнять JS на своей стороне. Система автоматически определяет, даёт ли рендеринг новый ценный контент — и если да, включает его для страницы. Владелец сайта может управлять этим вручную через Вебмастер: «рендерить», «не рендерить» или «на усмотрение робота» (по умолчанию).
- Рендеринг создаёт дополнительную нагрузку на сервер — робот запрашивает все скрипты и ресурсы страницы.
- Если скрипты закрыты через robots.txt, рендеринг не будет работать корректно — нужны директивы Allow или Clean-param.
- Яндекс прямо указывает: рендеринг на стороне робота не даёт преимуществ перед SSR или пререндерингом. Предпочтительный путь — отдавать боту готовый HTML.
Сравнение: что реально видит бот в момент краулинга
- Raw HTML с сервера — это всё, что он берёт в момент краулинга
- JS-файлы сохраняются для последующего рендеринга
- Контент внутри React/Vue/Angular — в момент краулинга недоступен
- Авторитетные сайты: рендеринг — часы или 1–2 дня
- Средние сайты: дни или до 1–2 недель
- Новые/малоавторитетные: недели, иногда месяц+
- Ошибки JS → страница остаётся без рендеринга неограниченно
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) | Статика с обновлением по расписанию. Хороший компромисс для контента средней динамики: категории, карточки. |
Топ-5 паттернов, которые ломают JS SEO
Контент за lazy load
Бот не прокручивает страницу. Если важный контент загружается при скролле — бот его не видит. Частая жертва: отзывы, рекомендации, связанные статьи. Решение: контент должен присутствовать в DOM при рендеринге.
Контент в табах и аккордеонах
Если контент скрыт за вкладками и не присутствует в DOM до взаимодействия — бот его не учитывает. Контент всех табов должен быть в DOM (можно CSS-скрытый), а не подгружаться по клику.
Ссылки, генерируемые JavaScript
Если навигационные или контентные ссылки рендерятся после загрузки JS — граф ссылок может быть неполным. Критично для меню, перелинковки, infinite scroll.
Мета-теги, устанавливаемые на клиенте
Title, description, canonical, hreflang — если они устанавливаются через JS после загрузки страницы, есть риск задержки. SSR-рендеринг этих данных убирает риск полностью.
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 |
Три шага диагностики + код для консоли
Простой тест для проверки расхождения между ботом и пользователем. Никакого специального инструмента не нужно:
Три версии одной страницы
View Source в браузере (Ctrl+U). Что отдаёт сервер без рендеринга. Это то, что бот получает в первый момент. Ищите: есть ли h1, title, основной текст, ссылки — или только пустой каркас с <div id="root">.
DevTools → Elements (F12). Что видит браузер после полного рендеринга. Это то, что видит пользователь. Сравните с Шагом 1: сколько текста добавил JS?
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();
})();
Приоритетный план исправлений
Диагностика трёх версий HTML
По репрезентативной выборке шаблонов. Не смотреть случайные страницы — смотреть коммерческие шаблоны с наибольшим потенциалом.
Составить список расхождений
Проблемные шаблоны с описанием симптомов. Это готовое техзадание для разработки.
Выбрать 1–2 SEO-критичных шаблона
С наибольшим потенциалом. Не чинить всё подряд — начать с «денежных» страниц.
Исправить критичный контент
Мета-теги, h1, основной текст, навигационные ссылки. Эти элементы нужны ботам в исходном HTML.
Решить вопрос с lazy load
На SEO-важных блоках. Либо убрать lazy load, либо обеспечить присутствие контента в DOM при инициальном рендеринге.
Оценить переход на SSR/ISR
Для приоритетных шаблонов. SSR нужен не везде — только там, где есть критичный для ранжирования контент, недоступный ботам.
Проверю рендеринг на 10 ваших URL
Raw HTML, rendered HTML, bot view — и покажу, где расхождение. Конкретный список проблем и порядок их устранения, а не абстрактные рекомендации.
Написать в Telegram