Настройка 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
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Настройка Dynamic Rendering для JavaScript-сайтов (Rendertron/Puppeteer)

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

Когда применять Dynamic Rendering

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

Не применять: если SSR/SSG доступен — Dynamic Rendering второсортное решение с накладными расходами.

Архитектура

Request → nginx → User-Agent check
                      ↓
          Bot? → Prerender Service (Puppeteer)
                      ↓
                  HTML snapshot → Bot
                      ↓
          Human? → SPA bundle → Browser

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
// ... after data loaded:
window.prerenderReady = true

// Puppeteer дожидается:
await page.waitForFunction(() => window.prerenderReady === true)

Срок выполнения

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