SSR vs CSR — что выбрать для веб-проекта

SSR формирует HTML на сервере, а CSR собирает интерфейс в браузере пользователя. На словах разница простая, но на практике выбор влияет на скорость первой загрузки, SEO, стоимость инфраструктуры и сложность разработки. Разберём оба подхода без магии и выясним, почему большинству современных проектов нужен не один режим, а их разумное сочетание.


Введение

Представим обычную страницу каталога интернет-магазина. Пользователь открывает ссылку и ожидает увидеть товары, цены и фотографии. Но вместо каталога несколько секунд смотрит на пустой экран или крутящийся индикатор загрузки.

Разработчик открывает DevTools и видит знакомую картину: сначала загрузился HTML с одним <div id="root"></div>, затем приехал JavaScript размером в несколько сотен килобайт, после этого приложение запустилось, отправило запрос к API и только тогда показало товары.

Это типичный сценарий клиентского рендеринга — CSR.

Можно сделать иначе. Сервер сам запросит данные, сформирует готовый HTML с товарами и отправит его браузеру. Пользователь почти сразу увидит содержимое страницы, а JavaScript подключится позже и добавит интерактивность.

Это уже SSR.

На этом месте часто появляется слишком простое правило: для SEO нужен SSR, а для личного кабинета — CSR. В целом направление верное, но реальный выбор немного сложнее. SSR не гарантирует быстрый сайт, а CSR не означает, что поисковые системы вообще ничего не увидят.

Главный вопрос звучит не так:

Что лучше — SSR или CSR?

Гораздо полезнее спросить:

Какие части интерфейса должны быть готовы до загрузки JavaScript, а какие действительно нужно выполнять в браузере?

Что такое CSR

CSR расшифровывается как Client-Side Rendering — рендеринг на стороне клиента.

При таком подходе сервер обычно отправляет браузеру минимальный HTML:

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <title>Каталог</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Самого каталога в документе пока нет. Браузер загружает JavaScript, запускает React, Vue или другой фреймворк, отправляет запрос к API и только после этого создаёт нужные элементы страницы.

MDN определяет CSR именно как создание HTML-контента с помощью JavaScript в браузере.

На React это может выглядеть так:

import { useEffect, useState } from 'react';

type Product = {
  id: number;
  name: string;
  price: number;
};

export function ProductsPage() {
  const [products, setProducts] = useState<Product[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    async function loadProducts() {
      try {
        const response = await fetch('/api/products', {
          signal: controller.signal,
        });

        if (!response.ok) {
          throw new Error(`Ошибка API: ${response.status}`);
        }

        const data: Product[] = await response.json();
        setProducts(data);
      } catch (error) {
        if (error instanceof Error && error.name !== 'AbortError') {
          setError(error.message);
        }
      } finally {
        setIsLoading(false);
      }
    }

    loadProducts();

    return () => controller.abort();
  }, []);

  if (isLoading) {
    return <p>Загружаем товары…</p>;
  }

  if (error) {
    return <p>Не удалось загрузить каталог: {error}</p>;
  }

  return (
    <main>
      <h1>Каталог</h1>

      <ul>
        {products.map((product) => (
          <li key={product.id}>
            {product.name} — {product.price} ₽
          </li>
        ))}
      </ul>
    </main>
  );
}

Код понятный, frontend и backend разделены, а приложение можно разместить как набор статических файлов на CDN. Для внутренних систем это часто отличный вариант.

Но до выполнения useEffect данных на странице нет. Сначала должен загрузиться и выполниться JavaScript, затем нужно дождаться ответа API. На быстром компьютере и хорошем соединении задержка может быть почти незаметной. На слабом смартфоне через мобильную сеть ситуация будет уже не такой радостной.

Где CSR действительно удобен

Клиентский рендеринг хорошо подходит для интерфейсов, в которых пользователь много взаимодействует с данными после входа в систему.

Например, для CRM, редактора документов, панели аналитики, конструктора сайта или административной части интернет-магазина. Такие страницы редко должны индексироваться поисковиками, зато в них постоянно меняются фильтры, таблицы, вкладки и графики.

После первой загрузки CSR-приложение может работать очень плавно. Переходы между разделами происходят без полной перезагрузки документа, а интерфейс обновляется точечно.

Кроме того, статическую сборку React-приложения можно разместить почти где угодно: на CDN, объектном хранилище или простом хостинге. Необязательно поднимать Node.js-сервер только ради отображения интерфейса.

На практике это заметно упрощает инфраструктуру. Особенно когда backend уже существует отдельно и предоставляет REST API или GraphQL.

Что такое SSR

SSR расшифровывается как Server-Side Rendering — рендеринг на стороне сервера.

В этом случае браузер отправляет запрос, сервер получает нужные данные, формирует HTML и возвращает уже заполненную страницу. MDN описывает SSR как генерацию HTML на сервере с последующей отправкой результата клиенту.

Упрощённо процесс выглядит так:

Браузер
   ↓ запрос /products
Сервер
   ↓ запрос данных
API или база данных
   ↓ товары
Сервер формирует HTML
   ↓
Браузер получает страницу с товарами

В Next.js App Router страницы и layout-компоненты по умолчанию являются серверными компонентами. Они могут получать данные на сервере, а клиентские компоненты подключаются там, где нужны состояние, обработчики событий или браузерные API.

Пример страницы каталога:

type Product = {
  id: number;
  name: string;
  price: number;
};

async function getProducts(): Promise<Product[]> {
  const response = await fetch('https://api.example.com/products', {
    cache: 'no-store',
  });

  if (!response.ok) {
    throw new Error(`Не удалось получить товары: ${response.status}`);
  }

  return response.json();
}

export default async function ProductsPage() {
  const products = await getProducts();

  return (
    <main>
      <h1>Каталог</h1>

      <ul>
        {products.map((product) => (
          <li key={product.id}>
            {product.name} — {product.price} ₽
          </li>
        ))}
      </ul>
    </main>
  );
}

Пользователь получает HTML, в котором уже присутствуют заголовок, названия товаров и цены. Ему не нужно ждать, пока React запустится и выполнит дополнительный запрос из useEffect.

Но здесь есть важный нюанс: SSR не означает, что JavaScript больше не нужен.

Если на странице есть кнопки, формы, модальные окна и другие интерактивные элементы, браузер должен загрузить клиентский код и связать его с уже существующим HTML. В React этот процесс называется гидратацией. Для серверного HTML React использует hydrateRoot, чтобы сделать разметку интерактивной.

До завершения гидратации пользователь может видеть кнопку, но обработчик её нажатия ещё не обязательно готов. Вот здесь чаще всего и ломается красивое обещание «SSR моментально показывает страницу». Показывает — да. Но полностью рабочей она становится после загрузки необходимого JavaScript.

SSR и Server Components — не совсем одно и то же

В современных React-проектах термины иногда смешиваются.

Классический SSR означает, что HTML страницы создаётся на сервере для запроса пользователя. React Server Components решают немного другую задачу: позволяют выполнять отдельные компоненты только на сервере и не отправлять их JavaScript в клиентский bundle.

Серверный компонент может получить данные и сформировать часть интерфейса:

import { AddToCartButton } from './AddToCartButton';

export default async function ProductPage() {
  const product = await getProduct();

  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <strong>{product.price} ₽</strong>

      <AddToCartButton productId={product.id} />
    </article>
  );
}

А интерактивную кнопку можно оставить клиентской:

'use client';

import { useState } from 'react';

type Props = {
  productId: number;
};

export function AddToCartButton({ productId }: Props) {
  const [isAdded, setIsAdded] = useState(false);

  async function addToCart() {
    const response = await fetch('/api/cart', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ productId }),
    });

    if (!response.ok) {
      throw new Error('Не удалось добавить товар');
    }

    setIsAdded(true);
  }

  return (
    <button type="button" onClick={addToCart} disabled={isAdded}>
      {isAdded ? 'Товар добавлен' : 'Добавить в корзину'}
    </button>
  );
}

В итоге описание товара формируется на сервере, а в браузер отправляется JavaScript только для кнопки и других интерактивных участков.

Именно такой подход обычно разумнее, чем превращать всю страницу либо в полностью серверную, либо в полностью клиентскую.

Что быстрее: SSR или CSR

Однозначного ответа здесь нет. Скорость состоит из нескольких разных этапов, и каждый подход выигрывает на своём.

При CSR сервер может очень быстро вернуть маленький HTML-файл. Но затем браузеру нужно загрузить JavaScript, разобрать его, выполнить приложение и запросить данные. Поэтому содержимое часто появляется не сразу.

При SSR сервер сначала получает данные и создаёт HTML. Из-за этого ответ может идти дольше, зато браузер получает уже готовый контент.

Получается небольшой парадокс:

  • CSR может быстро получить первый ответ, но поздно показать полезное содержимое;
  • SSR может дольше готовить ответ, но раньше показать саму страницу.

Важна и нагрузка на устройство пользователя. Чем больше JavaScript приходится выполнять в браузере, тем заметнее разница между мощным ноутбуком разработчика и недорогим смартфоном реального посетителя.

Но SSR тоже легко испортить. Если сервер последовательно делает пять медленных запросов к разным сервисам, а затем отправляет клиенту огромный bundle для гидратации, страница быстрой не станет. Просто индикатор загрузки переедет из браузера на сервер.

Поэтому SSR обычно требует:

  • кэширования данных;
  • ограничения количества серверных запросов;
  • параллельного получения независимых данных;
  • потоковой отдачи страницы;
  • разумного разделения серверных и клиентских компонентов.

Next.js поддерживает потоковый рендеринг и позволяет постепенно отправлять готовые части интерфейса, не дожидаясь завершения всей страницы.

Как SSR и CSR влияют на SEO

Именно SEO чаще всего становится главным аргументом в пользу SSR.

При серверном рендеринге поисковый робот сразу получает HTML с заголовком, текстом, ссылками и основным содержимым. Ему не нужно выполнять приложение, чтобы понять, о чём страница.

CSR сложнее, но утверждение «Google не индексирует JavaScript» давно устарело. Google умеет выполнять JavaScript и индексировать содержимое после рендеринга. При этом официальная документация отдельно предупреждает, что контент должен присутствовать в итоговом отрисованном HTML, а заблокированные, ошибочные или недоступные JavaScript-ресурсы могут помешать обработке страницы.

Иными словами, CSR может нормально индексироваться. Но появляется больше условий, которые должны сработать:

  1. JavaScript должен успешно загрузиться.
  2. Приложение не должно упасть при запуске.
  3. API должен вернуть данные.
  4. Контент должен появиться в DOM.
  5. Метаданные, ссылки и канонические адреса должны быть сформированы корректно.

В SSR-странице основное содержимое уже находится в ответе сервера. Поэтому для статей, категорий, карточек товаров, лендингов и других поисковых страниц серверный или статический рендеринг обычно остаётся более предсказуемым решением.

Есть ещё социальные сети, мессенджеры и различные парсеры. Не каждый бот будет полноценно запускать JavaScript ради получения заголовка, описания и изображения страницы. Поэтому корректные серверные метаданные полезны не только для Google.

Нагрузка на сервер

У CSR есть очевидное инфраструктурное преимущество: frontend можно раздать как статические файлы.

Пусть CDN отдаёт index.html, CSS и JavaScript, а основная нагрузка приходится на отдельный API. Сам frontend-сервер почти ничего не вычисляет.

При SSR каждый динамический запрос может потребовать:

  • запуска серверного кода;
  • запроса данных;
  • построения дерева компонентов;
  • генерации HTML;
  • отправки результата клиенту.

При большом трафике это уже реальные процессорное время и деньги.

Но и здесь не стоит делать вывод, что SSR обязательно дорогой. Страницы и данные можно кэшировать, часть контента можно генерировать заранее, а динамически обрабатывать только действительно персонализированные запросы.

Обычно всё ломается не из-за самого SSR, а из-за попытки рендерить на каждый запрос то, что меняется раз в сутки.

Если каталог обновляется каждые несколько часов, нет смысла заново собирать его для каждого посетителя. Здесь лучше использовать статическую генерацию, кэширование или периодическую ревалидацию.

Сложность разработки

CSR-приложение обычно проще запустить.

Есть React, API и несколько состояний: загрузка, ошибка, данные. Код выполняется только в браузере, поэтому разработчик свободно использует window, document, localStorage и другие Web API.

В SSR появляется два окружения:

  • сервер;
  • браузер.

И один и тот же компонент не всегда может работать в обоих.

Например, такой код упадёт на сервере:

const theme = localStorage.getItem('theme');

На сервере нет localStorage. Там вообще нет браузера.

Приходится явно отделять клиентские компоненты:

'use client';

import { useEffect, useState } from 'react';

export function ThemeSwitcher() {
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    const savedTheme = localStorage.getItem('theme');

    if (savedTheme) {
      setTheme(savedTheme);
    }
  }, []);

  return (
    <button
      type="button"
      onClick={() => {
        const nextTheme = theme === 'light' ? 'dark' : 'light';

        setTheme(nextTheme);
        localStorage.setItem('theme', nextTheme);
      }}
    >
      Тема: {theme}
    </button>
  );
}

Появляются и ошибки гидратации. Сервер сформировал один HTML, а первый клиентский рендер создал другой — React сообщает о несовпадении.

Классический пример:

export function CurrentTime() {
  return <span>{new Date().toLocaleTimeString()}</span>;
}

Сервер вычислил время в один момент, браузер — чуть позже. Значения отличаются.

То же самое происходит со случайными числами, проверками ширины окна, локалью, часовым поясом и данными из браузерного хранилища.

SSR требует чуть большей дисциплины. Нужно понимать, какой код где выполняется, какие данные должны совпадать и какие компоненты действительно обязаны попасть в клиентский bundle.

Сравнение SSR и CSR

КритерийSSRCSR
Первое содержимоеHTML приходит с сервераПоявляется после запуска JavaScript
SEOОбычно проще и предсказуемееВозможно, но зависит от корректного JS-рендеринга
ИнтерактивностьТребует клиентского кода и гидратацииЕстественная часть приложения
Нагрузка на серверВыше без кэшированияFrontend можно раздавать статически
Требования к устройствуМеньше работы для первого отображенияБольше работы выполняет браузер
ИнфраструктураНужен runtime, serverless или серверДостаточно статического хостинга
СложностьДва окружения и возможные ошибки гидратацииОбычно проще начать
Персонализированный интерфейсВозможен, но усложняет кэшированиеУдобен после авторизации

Таблица помогает увидеть направление, но выбирать архитектуру только по ней всё равно не стоит. Внутри одного проекта требования страниц могут сильно различаться.

Когда выбирать SSR

SSR стоит рассматривать, если содержимое страницы должно быть доступно сразу при открытии ссылки.

Типичные примеры — интернет-магазин, новостной сайт, блог, каталог услуг, доска объявлений, публичный профиль, страница мероприятия или лендинг.

Особенно полезен SSR, когда:

  • контент зависит от запроса и часто меняется;
  • страница должна индексироваться;
  • важна корректная генерация метаданных;
  • пользователь приходит на страницу из поиска;
  • нужно показывать актуальные данные без дополнительного клиентского запроса;
  • значительная часть аудитории использует слабые устройства.

Но не нужно автоматически рендерить на сервере каждую кнопку и выпадающий список. Серверу стоит оставить получение данных и формирование основного контента, а браузеру — взаимодействие с пользователем.

Когда выбирать CSR

CSR хорошо подходит для закрытых приложений, где SEO не играет роли, а большая часть работы начинается после авторизации.

Например:

  • CRM;
  • административная панель;
  • таск-менеджер;
  • редактор;
  • личный кабинет;
  • система аналитики;
  • внутренний корпоративный сервис.

Допустим, пользователь открыл панель управления рекламными кампаниями. Он будет менять фильтры, переключать графики, редактировать таблицы и отправлять формы. Перерисовывать всю страницу на сервере при каждом действии здесь бессмысленно.

CSR также удобен, когда frontend должен быть полностью независим от backend и распространяться как статическая сборка.

А что выбрать для интернет-магазина

Для магазина разумно использовать гибридный подход.

Главная страница, категории и карточки товаров должны приходить с готовым содержимым. Это полезно для первой загрузки, SEO и превью ссылок.

Фильтры, сортировка, избранное, корзина и выбор характеристик можно выполнять на клиенте.

На практике архитектура может выглядеть так:

Сервер:
— заголовок и описание категории;
— список товаров;
— цены;
— характеристики;
— SEO-метаданные.

Клиент:
— переключение фильтров;
— добавление в корзину;
— избранное;
— сравнение;
— модальные окна;
— обновление отдельных блоков.

Такой подход не заставляет выбирать между двумя крайностями. Пользователь получает готовую страницу, а затем работает с ней как с обычным интерактивным приложением.

А что выбрать для личного кабинета

Личный кабинет часто можно делать преимущественно на CSR.

Его содержимое персонализировано, закрыто авторизацией и обычно не должно появляться в поиске. Пользователь всё равно сначала проходит вход, после чего приложение загружает его заказы, настройки или статистику.

Однако даже здесь SSR иногда полезен. Например, сервер может проверить сессию до отправки страницы и сразу перенаправить неавторизованного пользователя. Также он может передать начальные данные, чтобы кабинет не начинался с нескольких последовательных загрузчиков.

То есть даже закрытая система не обязана быть полностью клиентской.

Типичные ошибки

Выбирать SSR только ради SEO

Если на сайте пять публичных страниц, а всё остальное представляет собой закрытую панель управления, переводить всё приложение на SSR необязательно.

Можно серверно или статически сформировать публичные страницы, а саму панель оставить клиентской.

Делать весь проект клиентским по привычке

Разработчик запускает Vite, создаёт React-приложение и только через несколько месяцев обнаруживает, что карточки товаров плохо выглядят без JavaScript, метаданные одинаковые на всех URL, а пользователи слишком долго смотрят на skeleton.

Архитектурное решение уже принято, просто никто не заметил момент, когда это произошло.

Считать SSR заменой оптимизации

Серверный рендеринг не исправляет тяжёлые изображения, огромный JavaScript, медленное API и сотни сторонних скриптов.

Можно создать SSR-страницу, которая сначала две секунды ждёт сервер, а затем ещё три секунды гидратирует интерфейс. Формально SSR есть. Пользователю от этого не легче.

Отправлять слишком много клиентского JavaScript

Если компонент не использует состояние, события или браузерные API, ему часто незачем становиться клиентским.

В Next.js директива 'use client' задаёт границу клиентской части, поэтому её не стоит без необходимости добавлять в корневой layout или крупный компонент страницы. Официальная документация рекомендует размещать клиентские границы точечно и вкладывать клиентские компоненты внутрь серверных.

Забывать про состояния загрузки

SSR тоже может ждать данные. Если один блок страницы загружается долго, пользователь не должен видеть пустой экран.

Используйте потоковую отдачу, Suspense, skeleton-компоненты и независимую загрузку отдельных участков интерфейса.

Путать SSR со статической генерацией

Если страница не меняется для каждого запроса, возможно, SSR ей вообще не нужен.

Статью блога можно сформировать во время сборки. Категорию товаров — обновлять по расписанию. Динамически рендерить стоит только те данные, для которых действительно важна актуальность на момент запроса.

Как принять решение

Начните не с выбора фреймворка, а с требований конкретной страницы.

Спросите себя:

  1. Должен ли основной контент индексироваться?
  2. Что увидит пользователь до загрузки JavaScript?
  3. Меняется ли содержимое для каждого запроса?
  4. Есть ли на странице персональные данные?
  5. Насколько много интерактивности?
  6. Можно ли кэшировать результат?
  7. Нужны ли браузерные API?
  8. Как страница работает на слабом устройстве и медленной сети?

Для публичной статьи ответы обычно ведут к статическому или серверному рендерингу.

Для редактора изображений — к CSR.

Для интернет-магазина — к сочетанию нескольких подходов.

Практическая стратегия для нового проекта

Если проект создаётся на React и заранее понятно, что в нём будут публичные SEO-страницы, я бы не начинал с полностью клиентского SPA. Лучше сразу выбрать фреймворк, поддерживающий серверные компоненты, статическую генерацию и клиентскую интерактивность.

При этом не нужно превращать сервер в универсальное решение всех проблем.

Хорошая базовая схема выглядит так:

  • страницы и получение начальных данных выполняются на сервере;
  • редко меняющийся контент кэшируется или генерируется заранее;
  • интерактивные элементы становятся небольшими клиентскими компонентами;
  • повторные запросы и локальные обновления интерфейса выполняются в браузере;
  • закрытые разделы могут работать преимущественно как CSR-приложение.

Современный Next.js как раз продвигает гибридную модель: серверные и клиентские компоненты можно комбинировать в одном дереве интерфейса, выбирая окружение не для всего приложения, а для конкретной части страницы.

Вывод

SSR и CSR решают разные задачи.

SSR полезен, когда пользователь и поисковый робот должны сразу получить содержимое страницы. Он подходит для каталогов, статей, карточек товаров, лендингов и других публичных страниц.

CSR удобен для насыщенных интерактивных интерфейсов: личных кабинетов, CRM, редакторов, аналитических панелей и внутренних сервисов.

Но в реальном проекте редко нужно выбирать только один вариант.

Наиболее практичный подход — сформировать основной контент на сервере или заранее, а интерактивность добавить на клиенте. Тогда браузеру не приходится строить всю страницу с нуля, а сервер не занимается тем, что удобнее выполнить непосредственно у пользователя.

Поэтому ответ на вопрос «SSR или CSR?» обычно звучит так:

SSR — для первого содержимого и публичных страниц. CSR — для взаимодействия после загрузки. А для большинства современных проектов — разумный гибрид.

Последние статьи