Налаштування Dynamic Rendering для JavaScript-сайтів (Rendertron/Puppeteer)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування Dynamic Rendering для JavaScript-сайтів (Rendertron/Puppeteer)
Складна
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування Dynamic Rendering для JavaScript-сайтів (Rendertron/Puppeteer)

Dynamic Rendering — підхід, при якому користувачам відправляється SPA, а пошуковим ботам — попередньо відрендерений HTML. Google офіційно визнає це прийнятним обходом, коли SSR недоступний.

Коли застосовувати Dynamic Rendering

  • SPA на React/Vue/Angular без SSR
  • Неможливість переписати застосунок під SSR
  • Тимчасове рішення до впровадження SSR
  • Окремі розділи сайту з проблемами індексації

Не застосовувати: якщо доступні SSR/SSG — Dynamic Rendering це другосортне рішення з накладними витратами.

Архітектура

Запит → nginx → Перевірка User-Agent
                      ↓
          Бот? → Prerender Service (Puppeteer)
                      ↓
                  HTML снапшот → Бот
                      ↓
          Людина? → SPA bundle → Браузер

Rendertron: self-hosted prerender

git clone https://github.com/GoogleChrome/rendertron
cd rendertron
npm install
npm run build

# Docker
docker build -t rendertron .
docker run -p 3000:3000 rendertron
# nginx: визначити ботів та відправити на Rendertron
map $http_user_agent $prerender_ua {
    ~*(Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|LinkedInBot|
       facebookexternalhit|Twitterbot|WhatsApp|TelegramBot|Slackbot)  1;
    default 0;
}

map $args $prerender_args {
    ~*_escaped_fragment_= 1;
    default 0;
}

map $prerender_ua$prerender_args $prerender {
    "11" 1;
    "10" 1;
    "01" 1;
    default 0;
}

server {
    listen 80;
    server_name company.com;

    location / {
        if ($prerender = 1) {
            rewrite .* /index.html break;
            proxy_pass http://rendertron:3000/render/https://company.com$request_uri;
        }
        try_files $uri /index.html;
    }
}

Кастомний prerender на Puppeteer

Більше контролю над поведінкою рендерингу:

// prerender-server.js
const express = require('express')
const puppeteer = require('puppeteer')

const app = express()
let browser = null

async function getBrowser() {
  if (!browser) {
    browser = await puppeteer.launch({
      headless: 'new',
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
      ]
    })
  }
  return browser
}

app.get('/render', async (req, res) => {
  const url = req.query.url
  if (!url) return res.status(400).send('URL required')

  try {
    const browser = await getBrowser()
    const page = await browser.newPage()

    // Заблокувати медіа для прискорення
    await page.setRequestInterception(true)
    page.on('request', req => {
      const type = req.resourceType()
      if (['image', 'media', 'font'].includes(type)) {
        req.abort()
      } else {
        req.continue()
      }
    })

    await page.goto(url, {
      waitUntil: 'networkidle0',
      timeout: 30000
    })

    // Дочекатися конкретного елемента (якщо потрібно)
    await page.waitForSelector('[data-ssr-ready]', { timeout: 10000 })
      .catch(() => {})  // не фатально якщо елемента немає

    const html = await page.content()
    await page.close()

    // Кешувати результат
    cache.set(url, html, 3600)  // 1 година

    res.set('Content-Type', 'text/html')
    res.send(html)

  } catch (err) {
    console.error(`Render failed for ${url}:`, err)
    res.status(500).send('Render failed')
  }
})

app.listen(3000)

Кешування результатів Prerender

Рендеринг кожної сторінки займає 1–5 секунд. Кеш обов'язковий:

const Redis = require('ioredis')
const redis = new Redis({ host: 'redis' })

async function cachedRender(url) {
  const cacheKey = `prerender:${url}`
  const cached = await redis.get(cacheKey)
  if (cached) return cached

  const html = await render(url)
  await redis.setex(cacheKey, 3600, html)
  return html
}

// Інвалідувати кеш при деплої
async function invalidateCache(pathPattern) {
  const keys = await redis.keys(`prerender:*${pathPattern}*`)
  if (keys.length) await redis.del(keys)
}

Middleware в Express/Fastify

// middleware/prerender.js
const botUserAgents = [
  'googlebot', 'bingbot', 'yandexbot', 'baiduspider',
  'facebookexternalhit', 'twitterbot', 'linkedinbot'
]

function isBot(userAgent) {
  return botUserAgents.some(bot =>
    userAgent.toLowerCase().includes(bot)
  )
}

module.exports = async function prerenderMiddleware(req, res, next) {
  const ua = req.headers['user-agent'] || ''

  if (!isBot(ua)) return next()  // людина — звичайна SPA

  try {
    const renderedHtml = await cachedRender(`https://${req.hostname}${req.originalUrl}`)
    res.set('X-Prerendered', 'true')
    res.send(renderedHtml)
  } catch (err) {
    // Fallback: відправити звичайну SPA (краще ніж 500)
    next()
  }
}

Prerender.io як SaaS альтернатива

# Використання Prerender.io без self-hosted рішення
location / {
    if ($prerender = 1) {
        proxy_pass https://service.prerender.io/https://company.com$request_uri;
        proxy_set_header X-Prerender-Token "YOUR_TOKEN";
    }
    try_files $uri /index.html;
}

Моніторинг та відладка

# Перевірити що бот отримує відрендерений HTML
curl -A "Googlebot/2.1 (+http://www.google.com/bot.html)" \
  https://company.com/product/42 | grep -c "product-title"

# Повинен повернути число > 0 (елементи знайдені в HTML)
// Додати маркер що сторінка повністю відрендерилася
// (для умови завершення Puppeteer)
window.prerenderReady = false
// ... після завантаження даних:
window.prerenderReady = true

// Puppeteer чекає:
await page.waitForFunction(() => window.prerenderReady === true)

Тривалість

Налаштування Rendertron або кастомного Puppeteer prerender з nginx та Redis-кешем — 2–3 робочих дні.