SSR для индексации SPA: пошаговое руководство
SSR для индексации SPA: пошаговое руководство

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 картина другая:

  1. Робот запрашивает URL.

  2. Сервер возвращает HTML с пустым <body> или одним <div>.

  3. Робот должен выполнить JavaScript, подождать сетевые запросы к API, и только тогда увидеть контент.

  4. На это у него нет ни времени, ни гарантированных ресурсов.

Google официально говорит, что Googlebot умеет рендерить JavaScript. Но на практике это работает с задержками: страница может попасть в индекс через несколько дней или недель после первого обхода, а может и не попасть вовсе. YandexBot справляется с JS ещё хуже.

Результат: низкий органический трафик, отсутствие превью в соцсетях, и страницы, которые просто не существуют для поисковой выдачи. По опыту, переход с CSR на SSR даёт прирост органического трафика в диапазоне 50-80% на контентных проектах.

CSR, SSR, Prerendering и SSG: в чём разница

CSR, SSR, Prerendering и SSG: в чём разница
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

Настройка SSR для React: пошагово с Next.js
Настройка 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 и убивает производительность. Чтобы этого избежать:

  1. Убедитесь, что данные на сервере и клиенте идентичны.

  2. Не используйте window или document на уровне модуля, только внутри useEffect.

  3. Проверяйте предупреждения в консоли разработчика при первом запуске.

// Правильно: проверка среды
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

Настройка SSR для Angular: Angular Universal
Настройка 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:

  1. Введите URL страницы.

  2. Нажмите «Проверить».

  3. В разделе «Рендеринг» посмотрите, какой HTML видит Googlebot.

  4. Если 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
Чек-лист внедрения 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

FAQ
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, и убедитесь, что внутренняя перелинковка позволяет роботу дойти до всех страниц.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А что вы выбираете для проектов в 2026 году?
66.67%Server Side Rendering (Next.js / Nuxt.js)2
33.33%Static Site Generation (SSG)1
0%Гибридная модель (ISR или Edge-рендеринг)0
0%Client-Side Rendering (CSR)0
0%Хочу посмотреть результаты0
Проголосовали 3 пользователя. Воздержался 1 пользователь.