
Yandexbot заходит на ваш SPA сайт, получает пустой <div id="root"></div> и уходит. Именно так выглядит индексация большинства одностраничных приложений без SSR. Страницы не попадают в выдачу, органический трафик стоит на нуле, а команда недоумевает: сайт же работает.
Проблема не в качестве кода, а в архитектуре рендеринга. Поисковые роботы медленно или вообще не выполняют JavaScript, а значит, видят страницу до того, как ваш React или Vue успел что-то нарисовать. Настройка Server Side Rendering для индексации SPA приложений поисковиками решает эту проблему: HTML приходит уже готовым прямо с сервера.
Привет! Я Пётр Гришечкин, эксперт в области SEO для e-commerce. Последние 15 лет я проектирую системы кратного роста трафика для крупнейших сайтов. И последнее время пишу всякие околоSEO статьи – https://t.me/seo_and_sem
Это статья написано для начинающих frontend и backend разработчиков, которые хотят разобраться с технической SEO-оптимизацией. Здесь будут конкретные команды, примеры кода для React/Next.js, Vue/Nuxt.js и Angular, а также чек-лист внедрения.
Почему SPA плохо индексируются поисковиками
Single Page Application (SPA) – это приложение, где сервер отдаёт один пустой HTML-файл, а весь контент загружается и рендерится прямо в браузере через JavaScript. Такой подход называется CSR (Client-Side Rendering, рендеринг на стороне клиента).
Для пользователя всё выглядит нормально: браузер быстро загружает JS-бандл и рисует интерфейс. Но для Googlebot или YandexBot картина другая:
Робот запрашивает URL.
Сервер возвращает HTML с пустым
<body>или одним<div>.Робот должен выполнить JavaScript, подождать сетевые запросы к API, и только тогда увидеть контент.
На это у него нет ни времени, ни гарантированных ресурсов.
Google официально говорит, что Googlebot умеет рендерить JavaScript. Но на практике это работает с задержками: страница может попасть в индекс через несколько дней или недель после первого обхода, а может и не попасть вовсе. YandexBot справляется с JS ещё хуже.
Результат: низкий органический трафик, отсутствие превью в соцсетях, и страницы, которые просто не существуют для поисковой выдачи. По опыту, переход с CSR на SSR даёт прирост органического трафика в диапазоне 50-80% на контентных проектах.
CSR, SSR, Prerendering и SSG: в чём разница

Прежде чем настраивать SSR, важно понять, какой подход подходит (простите за масло маслянное) вашему проекту. Вот краткое сравнение четырёх основных стратегий рендеринга.
Параметр | CSR | SSR | Prerendering | SSG |
|---|---|---|---|---|
Где рендерится HTML | Браузер | Сервер (при каждом запросе) | Заранее, для нужных URL | Заранее, для всех страниц |
FCP / LCP | Медленный | Быстрый | Быстрый | Очень быстрый |
SEO | Плохой | Отличный | Хороший | Отличный |
Свежесть данных | Реальное время | Реальное время | Устаревшие данные | Устаревшие данные |
Нагрузка на сервер | Минимальная | Высокая | Низкая | Минимальная |
CSR подходит для внутренних инструментов, дашбордов и приложений за авторизацией, где SEO не нужен.
SSR нужен для страниц с динамическим контентом: каталоги товаров, новости, профили пользователей.
Prerendering (предварительная отрисовка) — компромисс: статический HTML генерируется для популярных страниц заранее. Подходит, если страниц немного и они меняются редко.
SSG (Static Site Generation) — генерация всех страниц при сборке. Идеально для блогов, лендингов, документации.
Метрики, которые улучшает SSR
Поисковые алгоритмы используют Core Web Vitals как фактор ранжирования. SSR напрямую влияет на три ключевых метрики:
FCP (First Contentful Paint) – время до появления первого контента на экране. SSR сокращает его, потому что браузер сразу получает готовый HTML.
LCP (Largest Contentful Paint) – время отрисовки главного блока страницы. Цель: до 2.5 секунды.
TTFB (Time to First Byte) – время ответа сервера. Для SSR важно держать его ниже 200 мс. При превышении помогает кэширование через Redis.
Кроме метрик, SSR решает проблему превью в социальных сетях: VK, Telegram и другие мессенджеры не выполняют JavaScript при формировании превью, и без SSR они видят пустую страницу.
Настройка SSR для React: пошагово с Next.js

Next.js — стандарт для SSR в экосистеме React. Он берёт на себя всю серверную логику, маршрутизацию и оптимизацию.
Далее по тексту будут краткие примеры кода, который вам надо адаптировать под свой проект.
Шаг 1. Создание проекта
npx create-next-app@latest my-ssr-app cd my-ssr-app npm run dev
Next.js по умолчанию использует SSR для страниц с getServerSideProps и SSG для статических страниц.
Шаг 2. Серверный запрос данных через getServerSideProps
// pages/product/[id].jsx export async function getServerSideProps(context) { const { id } = context.params; const res = await fetch(`https://api.example.com/products/${id}`); const product = await res.json(); if (!res.ok) { return { notFound: true }; // вернёт 404 для поисковика } return { props: { product }, }; } export default function ProductPage({ product }) { return ( <main> <h1>{product.name}</h1> <p>{product.description}</p> </main> ); }
При каждом запросе Next.js выполняет getServerSideProps на сервере, получает данные и вставляет их в HTML. Googlebot получает полностью заполненную страницу.
Шаг 3. Мета-теги для SEO
import Head from 'next/head'; export default function ProductPage({ product }) { return ( <> <Head> <title>{product.name} | Магазин</title> <meta name="description" content={product.shortDescription} /> <meta property="og:title" content={product.name} /> <meta property="og:image" content={product.imageUrl} /> </Head> <main> <h1>{product.name}</h1> </main> </> ); }
Шаг 4. Деплой
Для Next.js с SSR оптимальны платформы с поддержкой Node.js:
Vercel — нулевая конфигурация, рекомендован разработчиками Next.js.
Netlify с адаптером Next.js.
Собственный Node.js сервер с PM2 для продакшена.
Hydration: как клиент «оживляет» серверный HTML
После того как браузер получил HTML с сервера, React «подхватывает» его и делает интерактивным. Этот процесс называется hydration (гидратация).
Важный нюанс: если серверный и клиентский HTML не совпадают, React перерендерит всё дерево заново. Это называется hydration mismatch и убивает производительность. Чтобы этого избежать:
Убедитесь, что данные на сервере и клиенте идентичны.
Не используйте
windowилиdocumentна уровне модуля, только внутриuseEffect.Проверяйте предупреждения в консоли разработчика при первом запуске.
// Правильно: проверка среды const isBrowser = typeof window !== 'undefined'; // Неправильно: упадёт на сервере const width = window.innerWidth;
// Правильно: проверка среды const isBrowser = typeof window !== 'undefined'; // Неправильно: упадёт на сервере const width = window.innerWidth;
Настройка SSR для Vue с Nuxt.js
Nuxt.js делает для Vue то же, что Next.js для React: добавляет SSR, маршрутизацию и генерацию статики из коробки.
Шаг 1. Создание проекта
npx nuxi@latest init my-nuxt-app cd my-nuxt-app npm install npm run dev
Шаг 2. Серверные данные через useFetch
<!-- pages/product/[id].vue --> <script setup> const route = useRoute(); const { data: product, error } = await useFetch( `/api/products/${route.params.id}` ); if (error.value) { throw createError({ statusCode: 404, message: 'Не найдено' }); } useHead({ title: product.value?.name, meta: [ { name: 'description', content: product.value?.shortDescription }, ], }); </script> <template> <main> <h1>{{ product?.name }}</h1> <p>{{ product?.description }}</p> </main> </template>
Шаг 3. Конфигурация nuxt.config.ts
export default defineNuxtConfig({ ssr: true, // включён по умолчанию в Nuxt 3 app: { head: { htmlAttrs: { lang: 'ru' }, link: [{ rel: 'canonical', href: 'https://example.com' }], }, }, nitro: { preset: 'node-server', // для деплоя на Node.js }, });
Для статической генерации замените ssr: true на ssr: false и добавьте команду npm run generate.
Настройка SSR для Angular: Angular Universal

Angular Universal (ныне встроен в Angular 17+ как стандартная возможность) добавляет серверный рендеринг к Angular-приложениям.
Шаг 1. Добавление SSR в существующий проект
# Angular 16 и ниже ng add @nguniversal/express-engine # Angular 17+ ng add @angular/ssr
Шаг 2. Структура после добавления SSR
Команда создаёт файл server.ts — это точка входа Express-сервера, который рендерит Angular на стороне сервера.
// server.ts (упрощённо) import { APP_BASE_HREF } from '@angular/common'; import { CommonEngine } from '@angular/ssr'; import express from 'express'; import { fileURLToPath } from 'url'; import { dirname, join, resolve } from 'path'; import AppServerModule from './src/app/app.server.module'; const server = express(); const serverDistFolder = dirname(fileURLToPath(import.meta.url)); const browserDistFolder = resolve(serverDistFolder, '../browser'); const commonEngine = new CommonEngine(); server.get('*', (req, res, next) => { commonEngine .render({ bootstrap: AppServerModule, documentFilePath: join(browserDistFolder, 'index.html'), url: req.originalUrl, publicPath: browserDistFolder, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }], }) .then((html) => res.send(html)) .catch(next); });
Шаг 3. Сборка и запуск
npm run build:ssr npm run serve:ssr
Приложение запускается на Node.js и отдаёт полный HTML при первом запросе.
Важный момент для Angular Universal: браузерные API (window, document, localStorage) недоступны на сервере. Используйте isPlatformBrowser для условного выполнения:
import { isPlatformBrowser } from '@angular/common'; import { inject, PLATFORM_ID } from '@angular/core'; const platformId = inject(PLATFORM_ID); if (isPlatformBrowser(platformId)) { // безопасно работать с window console.log(window.location.href); }
SSR с данными из Headless CMS
Если контент хранится в Strapi, Contentful или другой Headless CMS, SSR-запросы строятся так же: данные получаются на сервере до отрисовки HTML.
Пример для Next.js + GraphQL (Contentful):
export async function getServerSideProps({ params }) { const query = ` query GetArticle($slug: String!) { articleCollection(where: { slug: $slug }) { items { title body seo { title description } } } } `; const res = await fetch( `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL\\_SPACE\\_ID}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.CONTENTFUL_TOKEN}`, }, body: JSON.stringify({ query, variables: { slug: params.slug } }), } ); const { data } = await res.json(); const article = data?.articleCollection?.items?.[0]; if (!article) return { notFound: true }; return { props: { article } }; }
Отдельно убедитесь, что для несуществующих страниц сервер возвращает реальный HTTP 404, а не 200 с пустым контентом. Поисковики воспринимают «мягкие 404» как дублирующийся контент.
Тестирование индексации: инструменты

Google Search Console
После деплоя SSR используйте инструмент «Проверка URL» (URL Inspection) в Google Search Console:
Введите URL страницы.
Нажмите «Проверить».
В разделе «Рендеринг» посмотрите, какой HTML видит Googlebot.
Если HTML содержит контент, а не пустые теги – SSR работает корректно.
Lighthouse и PageSpeed Insights
Запустите Lighthouse до и после внедрения SSR. Смотрите на:
Оценку Performance (целевой показатель: 80+ для мобильных)
FCP и LCP в секундах
«Избегайте нескольких перенаправлений» и «Уменьшите время ответа сервера»
Screaming Frog
Полезен для массовой проверки: сканирует все страницы сайта и показывает мета-теги, статус ответа (200/301/404), дублирующиеся title и description. После настройки SSR запустите краулер и убедитесь, что каждая страница возвращает 200 с заполненным <title> и <h1>.
Масштабирование SSR: нагрузка и кэширование
SSR перекладывает рендеринг с браузера на сервер. При высоком трафике это создаёт нагрузку на Node.js процессы.
Что можетпомочь:
PM2 и кластеры Node.js
npm install pm2 -g pm2 start server.js -i max # запускает процесс на каждое ядро CPU
Кэширование через Redis
Для страниц, которые обновляются редко (например, карточка товара с ценой раз в час), кэшируйте HTML ответ:
// псевдокод серверного кэша app.get('/product/:id', async (req, res) => { const cacheKey = `product:${req.params.id}`; const cached = await redis.get(cacheKey); if (cached) return res.send(cached); const html = await renderPage(req.params.id); await redis.set(cacheKey, html, 'EX', 3600); // TTL 1 час res.send(html); });
Edge-рендеринг
Cloudflare Workers и Vercel Edge Runtime позволяют рендерить страницы на узлах CDN, географически близких к пользователю. Это сокращает TTFB без нагрузки на основной сервер. Next.js и Nuxt.js поддерживают edge runtime из коробки.
Чек-лист внедрения SSR

Перед тем как считать SSR внедрённым, пройдитесь по этому списку:
Базовая настройка
[ ] Сервер возвращает полный HTML с контентом (проверить через
curl -L URL | grep h1)[ ] HTTP-статусы корректны: 200 для существующих, 404 для несуществующих страниц
[ ] Нет hydration mismatch в консоли браузера
[ ] Время ответа сервера (TTFB) до 200 мс под нагрузкой
SEO-оптимизация
[ ] Каждая страница имеет уникальный
<title>[ ] Каждая страница имеет
<meta name="description">[ ] Настроены Open Graph теги (
og:title,og:description,og:image)[ ] Canonical URL задан корректно
[ ] robots.txt не закрывает нужные страницы
[ ] sitemap.xml обновляется автоматически и отправлен в Google Search Console
[ ] Нет дублирующихся мета-тегов
Технические проверки
[ ] Нет обращений к
window/documentвне браузерного контекста[ ] API-запросы из серверного кода защищены (нет утечки токенов в HTML)
[ ] Настроено кэширование для тяжёлых страниц
[ ] Лог ошибок сервера мониторится (Sentry, Datadog или аналоги)
После деплоя
[ ] Проверены ключевые URL через URL Inspection в Google Search Console
[ ] Запущен Lighthouse: Performance 80+ на мобильных
[ ] Screaming Frog не находит страниц с пустым
<title>[ ] Запрошена переиндексация в Google Search Console и Яндекс.Вебмастере
Итог: что реально даёт SSR
Server Side Rendering для индексации SPA приложений поисковиками – это не магия и не сложная операция. Это архитектурное решение, которое перемещает рендеринг туда, где поисковые роботы его гарантированно увидят.
Что вы получаете на практике:
Поисковики видят полный HTML с первого запроса.
FCP и LCP улучшаются, что влияет на ранжирование.
Превью в социальных сетях начинают работать.
Органический трафик растёт по мере переиндексации страниц.
Первый шаг: откройте любую страницу вашего SPA через curl URL в терминале. Если в ответе нет заголовка <h1> или основного текста – вы видите ровно то, что видит Googlebot. Это и есть точка старта.
Выберите фреймворк под ваш стек (Next.js для React, Nuxt.js для Vue, Angular Universal для Angular), создайте тестовую страницу с серверным запросом данных и проверьте HTML через URL Inspection в Search Console. Всё остальное – итерации поверх этого фундамента.
FAQ

Нужно ли мигрировать всё приложение на SSR сразу?
Нет. Next.js и Nuxt.js позволяют смешивать подходы: одни страницы рендерятся на сервере, другие – статически, третьи – на клиенте. Начните с самых важных для SEO страниц: главная, каталог, карточки товаров или статьи. Внутренние инструменты и дашборды за авторизацией можно оставить на CSR.
SSR или Prerendering – что выбрать для интернет-магазина?
Если у вас до нескольких сотен страниц с редко меняющимися данными – Prerendering проще и дешевле. Если ассортимент постоянно меняется, цены обновляются в реальном времени, и страниц тысячи – нужен SSR. На практике интернет-магазины часто используют SSG для категорий и SSR для карточек товаров.
Как SSR влияет на безопасность: не попадут ли токены API в HTML?
Это реальный риск. Серверный код выполняется в Node.js и имеет доступ к переменным окружения с токенами. Главное правило: данные, которые вы передаёте в props (Next.js) или возвращаете из useFetch (Nuxt.js), попадают в HTML. Никогда не передавайте API-ключи, секреты или данные других пользователей в props страницы. Запрашивайте только то, что нужно конкретному пользователю.
Как ускорить первый деплой SSR без полной переработки приложения?
Если переписывать приложение не вариант, используйте Prerendering-сервис (например, Prerender.io или аналоги с открытым кодом). Он перехватывает запросы от поисковых ботов, рендерит страницу headless-браузером и отдаёт статический HTML. Это не настоящий SSR, но решает проблему индексации без рефакторинга.
Почему страницы медленно попадают в индекс даже после внедрения SSR?
SSR не ускоряет сам процесс обхода со стороны Google: это зависит от краулингового бюджета (crawl budget – количество страниц, которые Googlebot готов обойти за единицу времени). Чтобы ускорить переиндексацию: отправьте обновлённый sitemap.xml в Google Search Console, используйте инструмент «Запросить индексацию» для ключевых URL, и убедитесь, что внутренняя перелинковка позволяет роботу дойти до всех страниц.
