Оптимизация рендеринга JavaScript для поисковых роботов (JavaScript SEO)
Googlebot и другие поисковые роботы по-разному обрабатывают JavaScript. Google выполняет JS через headless Chrome (WRS — Web Rendering Service), но с задержкой: HTML-страница индексируется немедленно, JS-рендеринг ставится в очередь на отдельный этап. Для SEO это означает, что контент, существующий только в DOM после выполнения JS, может задержаться с индексацией на дни или недели.
Как работает Google с JavaScript
Этап 1: Crawling — Googlebot скачивает исходный HTML.
Этап 2: Rendering queue — страница помещается в очередь для JS-рендеринга. Задержка: от нескольких часов до нескольких недель.
Этап 3: Indexing after render — контент после выполнения JS попадает в индекс.
Яндекс, Bing, DuckDuckGo — рендерят JS значительно хуже или вообще не рендерят.
Диагностика проблем
Google Search Console → URL Inspection → View Crawled Page — показывает финальный DOM после рендеринга Googlebot. Если важный контент присутствует там, но не индексируется — проблема не в рендеринге.
Screaming Frog с JS-рендерингом — сравнение HTML source с rendered DOM.
# Сравнить source HTML с rendered (через curl vs puppeteer)
curl -s https://site.com/page | grep -c "product-title"
# vs
node -e "const puppeteer = require('puppeteer'); (async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://site.com/page');
await page.waitForSelector('.product-title');
const count = await page.$$eval('.product-title', els => els.length);
console.log(count);
await browser.close();
})()"
Решение 1: SSR (Server-Side Rendering)
SSR — рендеринг страницы на сервере, бот получает готовый HTML без необходимости выполнять JS.
Next.js:
// getServerSideProps — рендеринг на каждый запрос
export async function getServerSideProps({ params }) {
const product = await fetchProduct(params.id)
return { props: { product } }
}
// getStaticProps — сборка при деплое (быстрее)
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id)
return {
props: { product },
revalidate: 3600 // ISR: обновлять раз в час
}
}
Nuxt.js:
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true, // включить SSR
})
// composable
const { data } = await useFetch(`/api/products/${id}`)
Решение 2: SSG (Static Site Generation)
Для контента с нечастыми обновлениями SSG даёт лучшую производительность:
// Next.js ISR (Incremental Static Regeneration)
export async function getStaticPaths() {
const products = await fetchAllProducts()
return {
paths: products.map(p => ({ params: { id: p.id } })),
fallback: 'blocking' // новые страницы рендерятся при первом запросе
}
}
Решение 3: Prerendering для SPA без SSR
Если SSR нельзя внедрить, prerendering генерирует статические HTML-снапшоты:
# nginx: отдавать prerendered HTML ботам, SPA — людям
map $http_user_agent $is_bot {
~*(googlebot|bingbot|yandex|baiduspider|facebookexternalhit) 1;
default 0;
}
server {
location / {
if ($is_bot = 1) {
proxy_pass http://prerender-service:3000;
break;
}
try_files $uri /index.html;
}
}
Критичные правила JS SEO
Содержимое в исходном HTML > содержимое в JS
<!-- Плохо: title генерируется только JS -->
<title>Loading...</title>
<script>document.title = fetchProductTitle()</script>
<!-- Хорошо: title в исходном HTML -->
<title>iPhone 15 Pro - купить в Москве | Shop.ru</title>
Ссылки в <a href>, а не только onClick:
// Плохо: нет href, бот не найдёт ссылку
<span onClick={() => navigate('/product/42')}>Товар</span>
// Хорошо
<a href="/product/42">Товар</a>
Lazy loading и SEO:
// Intersection Observer + lazy loading: контент ниже fold
// Googlebot не скроллит, поэтому lazy-loaded контент может не попасть в индекс
// Решение: использовать loading="lazy" только для изображений,
// но не для текстового контента
<img src="product.jpg" loading="lazy" alt="Product">
// Текст ниже fold — не применять lazy loading
JSON-LD вместо микроданных в атрибутах:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "iPhone 15 Pro",
"offers": {
"@type": "Offer",
"price": "99999",
"priceCurrency": "RUB"
}
}
</script>
Проверка через Google Rich Results Test
# API для проверки разметки
curl "https://search.google.com/test/rich-results/result?url=https://site.com/product/1"
Core Web Vitals и JavaScript
Долгий TBT (Total Blocking Time) из-за тяжёлого JS ухудшает Page Experience сигнал:
# Найти long tasks через Chrome DevTools Performance API
window.performance.getEntriesByType('longtask').forEach(task => {
if (task.duration > 50) {
console.warn(`Long task: ${task.duration}ms at ${task.startTime}`)
}
})
Срок выполнения
Аудит JS SEO + внедрение SSR/ISR для Next.js — 3–7 рабочих дней в зависимости от объёма приложения.







