Оптимізація рендерингу JavaScript для поисковых роботів (JavaScript SEO)
Googlebot та інші пошукові роботи по-різному обробляють JavaScript. Google виконує JS через headless Chrome (WRS — Web Rendering Service), але з затримкою: HTML-сторінки індексуються одразу, JS-рендеринг ставиться в окрему чергу. Для SEO це означає, що контент, що існує тільки в DOM після виконання JS, може затримуватися з індексацією на дні або тижні.
Як Google обробляє JavaScript
Етап 1: Краулинг — Googlebot завантажує вихідний HTML.
Етап 2: Черга рендерингу — сторінка помістається в чергу для JS-рендерингу. Затримка: від кількох годин до кількох тижнів.
Етап 3: Індексація після рендерингу — контент після виконання 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 робочих днів у залежності від розміру застосунку.







