javascript

SSR vs CSR vs гибрид. Сравниваем подходы к рендерингу страниц Django-приложения

  • среда, 11 февраля 2026 г. в 00:00:09
https://habr.com/ru/companies/selectel/articles/994576/

Решил один мой коллега (не разработчик) заняться пет-проектом: создать платформу с элементами соцсети и встроенным картографическим плагином. И вот приходит этот коллега ко мне с вопросом по части фронтенда: «А как бы мне отрендерить страницы: на сервере или на клиенте?», описывая то решение, которое он уже сделал и которое в целом работает.

Что ж. Меня зовут Андрей, я фронтенд-разработчик в Selectel. В этой статье я расскажу и покажу, какие виды рендеринга веб-страниц существуют и в каком случае лучше использовать тот или иной подход. Разберем все это дело на примерах. Сперва пройдемся по теоретическим основам, а затем — по практике. Если так сложилось, что и вас жизнь привела к подобным фронтендерным запросам, но должного опыта еще нет, добро пожаловать под кат.

Технологические рамки и немного базы

Сразу оговорюсь, что приложение должно представлять собой PWA, чтобы его можно было использовать на любой платформе (веб, iOS, Android) без нативной реализации отдельного приложения под каждую из них. В качестве языка для серверной части был взят Python и его фреймворк Django. Для клиента же будем использовать JavaScript.

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

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

Клиентский же рендеринг исповедует иной подход, при котором основная работа по генерации, загрузке и отображению пользовательского интерфейса всецело переходит браузеру клиента. Браузер запрашивает начальный скелет всего HTML-документа, JavaScript-бандл, CSS-стили, после чего динамически строит интерфейс и самостоятельно управляет состоянием приложения, его логикой и данными, используя загруженный JavaScript.

Серверный рендеринг

Server-Side Rendering (SSR)

Серверный рендеринг (SSR) — это классическая модель рендеринга страниц веб-приложения, при которой полный HTML-документ формируется на стороне сервера в ответ на каждый пользовательский запрос. Термин SSR может использоваться для обозначения как всеобъемлющей концепции серверного рендеринга, так и конкретного подхода (который корректнее называть Dynamic SSR).

Основной механизм работы можно разбить на несколько пунктов.

  1. Пользовательский запрос. На данном этапе браузер клиента делает запрос на сервер для получения HTML-документа.

  2. Серверная обработка. Серверная логика, реализованная на Python Flask, Django, Node.js, и т. д., определяет данные, которые необходимы для запроса. Сервер заполняет соответствующий шаблон (файл .html или .jinja, если используется шаблонизатор Jinja) актуальными данными.

  3. Рендеринг. Здесь происходит последовательное выполнение кода шаблона шаблонизатором (например, Jinja, или любой другой, в том числе, написанный самостоятельно), при котором выполняются все циклы и условия внутри шаблона. По окончании данного процесса шаблонизатор генерирует полностью готовый статичный HTML-документ (строку), который веб-браузер может отобразить.

  4. Ответ. На данном этапе сервер отправляет клиенту сгенерированный HTML-документ в ответе на его запрос.

  5. Отображение в браузере и гидратация. Браузер получает готовый HTML-документ и сразу его отображает. Зачастую, после отображения подгружаются и выполняются дополнительные JavaScript-файлы для реализации какой-то определенной пользовательской логики. Этот процесс называется «гидратацией», и мы рассмотрим его позднее.

Static-Site Generation (SSG)

SSG — это подход серверного рендеринга, который предполагает предварительный рендеринг страниц на сервере (либо во время сборки) для каждого URL приложения, в отличие от генерации страниц в ответ на пользовательский запрос. Механизм работы данного подхода можно описать следующим алгоритмом:

  1. Сборка и генерация страниц. Запускается сборка приложения (например, npm run build), в процессе которой генерируются HTML-страницы приложения для каждого роута. Далее собранные статические файлы (HTML, CSS, JS) размещаются на CDN для дальнейшей доставки их клиенту.

  2. Запрос от клиента. Когда браузер пользователя делает запрос на сервер (или CDN), сервер отдает уже готовый, заранее сгенерированный статический HTML-файл для указанной страницы. При этом в рантайме не происходит никакой работы сервера с данными.

Потоковый серверный рендеринг

Потоковый SSR (Streaming SSR) — это эволюционное и закономерное развитие Dynamic SSR-подхода. Это техника, при которой сервер не ждет полной генерации HTML-документа. Он начинает передавать документ браузеру клиента частями по мере их готовности (например, заголовок страницы, некоторые секции). Для этого используется потоковая передача данных на основе HTTP-потоков. Подход позволяет обеспечить быструю первую отрисовку (First Paint), а также первую содержательную отрисовку (FCP), так как HTML доставляется браузеру значительно быстрее.

Потоковый серверный рендеринг — одна из вариаций такого понятия как «гидратация». Механизм работы потокового SSR можно описать следующим образом.

  1. Запрос. Браузер клиента делает запрос на получение определенной страницы.

  2. Серверная обработка. На данном этапе сервер начинает обрабатывать пользовательский запрос, генерируя HTML-документ, связанный с запрашиваемой страницей. Однако, в отличие от классического SSR, здесь происходит разбиение документа на логические блоки — чанки.

  3. Передача чанков (кусочков) HTML-документа.

Последний шаг представляет наибольший интерес, поэтому есть смысл разобраться с ним подробнее.

  1. Мгновенная передача первого чанка. На этом шаге сервер отправляет браузеру начальную разметку документа, включающую !DOCTYPE html, тег <head> и навигацию (при ее наличии).

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

  3. Генерация остального контента. Сервер начинает генерацию для оставшегося контента, но только того, который не требуется в данный момент (например, блок с рекомендациями). В этот момент в HTML-поток вставляется специальный плейсхолдер для последующего заполнения.

  4. Завершение генерации. После создания оставшегося компонента HTML-документа, сервер отправляет его браузеру вместе с небольшим JavaScript-сценарием, который размещает присланный контент в документ на место placeholder.

Технологическая основа данного подхода — это возможность сервера отправлять HTTP-ответ частями с использованием специального параметра для HTTP-заголовка Transfer-Encoding: chunked. Браузер, в свою очередь, интерпретирует такой поток и отображает блоки HTML-документа по мере их загрузки.

Создайте веб-приложение и получите бонусы на его деплой в облако

В новом бесплатном курсе по JavaScript.

Подробнее →

Клиентский рендеринг

Клиентский рендеринг (Client-Side Rendering, CSR) — это стратегия, которая предполагает создание и обновление пользовательского интерфейса полностью на стороне браузера пользователя с использованием JavaScript. Сервер выполняет лишь функцию поставщика данных в определенном формате (зачастую, JSON) и каких-то вспомогательных статических файлов.

Алгоритм, реализующий данную стратегию, можно описать следующим образом.

  1. Запрос клиента. Браузер клиента делает на сервер начальный HTTP-запрос, в ответ на который сервер присылает начальный HTML-документ (оболочку). В нем указаны ссылки на JavaScript-сценарии и метаинформация.

  2. Загрузка скриптов. Браузер загружает JavaScript-сценарии, указанные в HTML-документе и начинает их исполнять. В процессе исполнения JavaScript происходит инициализация фреймворков, которые могут быть использованы (Angular, React и т. д).

  3. Получение данных. Далее JS-код приложения определяет, какой роут приложения загружен, а также то, какие данные необходимы для этого роута, и начинает их загрузку. Загрузка происходит посредством HTTP-запросов на сервер.

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

В случае использования какого-либо фронтенд-фреймворка в базовом сценарии после начальной и полной загрузки приложение работает по принципу SPA (Single Page Application). В этом случае при переходе по страницам не происходит полной перезагрузки приложения, потому что вся навигация была загружена на первом шаге. JavaScript подгружает только необходимые данные для роута и точечно обновляет DOM-дерево. При этом состояние приложения хранится в браузере пользователя.

Совмещение подходов SSR и CSR

Регидратация

Помимо рассмотренных выше видов серверного и клиентского рендеринга, существует еще дополнительный подход, который основывается на использовании сильных сторон каждой из парадигм. Этот метод называется универсальным и призван нивелировать недостатки каждого из подходов. Суть его — в использовании такого механизма как регидратация.

Регидратация (rehydration) — это процесс «оживления» статического HTML-документа, сгенерированного на сервере, путем подключения дополнительного JavaScript-кода. Код позволяет добавить недостающие обработчики, события, методы или даже элементы, которые не рендерились изначально на сервере. Помимо этого, данный подход предназначен и для последующего повторного рендеринга уже на стороне клиента.

Полная регидратация

Это самый распространенный и одновременно самый простой вид рендеринга. Можно сказать, стандарт для Angular, React и Vue.js при использовании SSR. Суть данного метода заключается в том, что сервер создает и отдает клиенту весь HTML-документ запрашиваемой страницы. Параллельно с этим клиент загружает и начинает исполнять JavaScript-бандл приложения. Затем загруженный JavaScript-код проходит по всему DOM-дереву загруженной страницы и привязывает все обработчики событий и состояния к компонентам. После этого страница становится полностью интерактивной и является по своей сути полноценным SPA (Single Page Application).

Преимуществом подхода можно назвать простоту использования, потому что современные фреймворки умеют делать это «из коробки». Также положительной особенностью можно отметить высокую скорость первой отрисовки (метрика First Contentful Paint).

Среди недостатков — долгая интерактивность и задержка взаимодействия. Пользователь видит статический документ, но не может с ним никак взаимодействовать, пока не будет загружен весь JavaScript-бандл и не будет оживлена загруженная страница. Если JavaScript-бандл большой, то время загрузки и, соответственно, регидратации увеличится соответственно.

Частичная регидратация

Это более сложный подход по сравнению с первым. Он направлен на устранение недостатков полной регидратации.

Как и при полной регидратации, сервер отдает клиенту полностью сгенерированный HTML-документ, но разбитый на независимые части. Клиент загружает JavaScript-код чанками (потоково) для каждой части или компонента на странице. При этом лишь часть компонентов будет регидрироваться сразу (обычно это хедер, навигация и т. д.), а остальные подъедут позже, по мере необходимости (или вовсе не подъедут).

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

Из недостатков — высокая сложность настройки и реализации, а также сложная сборка и необходимость разделения кода.

Прогрессивная регидратация

Этот метод — следствие эволюции частичной регидратации. В современной веб-разработке с использованием серверного рендеринга его можно назвать лучшей практикой. Подход тесно связан с такими технологиями как React Server Components, Nuxt 3 и Next.js.

Прогрессивная регидратация работает по следующему алгоритму:

  1. Сервер не ожидает генерации всего HTML-документа, а начинает отправлять его частями потоково по мере готовности. 

  2. Браузер клиента начинает отображать документ и загружать JavaScript для него также потоково.

  3. Далее браузер начинает «оживлять» компоненты страницы не в порядке их загрузки, а согласно приоритету. Сперва регидратируются компоненты, которые находятся в области видимости пользователя, а уже затем остальные — когда пользователь опустится до них. Некоторые компоненты могут в принципе остаться нерегидратированными.

Преимущество подхода — минимальное время до интерактивности (метрика Time to Interactive), что напрямую улучшает пользовательский опыт, поскольку в таком случае страница кажется крайне отзывчивой. Еще плюс — замечательная производительность, потому что потоковая отрисовка и регидратация компонентов позволяет сократить количество операций, блокирующих основной поток.

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

Пример приложения

Но хватит теории! Давайте наконец рассмотрим описанные выше технологии рендеринга на примере «эволюции» реального приложения. Подопытным будет проект моего коллеги, с которого все и началось.

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

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

Эволюция архитектуры веб-приложения: от серверного рендеринга к гибридной модели

Изначально проект задумывался как веб-приложение, ориентированное в первую очередь на пользователей мобильных устройств. При этом его автор хоть и не профессиональный разработчик, но обладает большим опытом в бэкенде (так сложилось). А вот для реализации клиентской части традиционными методами (например, React или Angular) ему потребовалось бы значительное время на освоение.

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

Первая итерация — классический серверный рендеринг (SSR)

На первом этапе платформа была реализована по классической схеме серверного рендеринга. В этой модели весь процесс формирования страницы происходил на стороне сервера:

  1. Сервер получал HTTP-запрос от клиента. 

  2. Бэкенд-логика (Django Views) обрабатывала запрос, извлекала необходимые данные из БД. 

  3. С помощью шаблонизатора Jinja данные подставлялись в HTML-шаблоны. 

  4. Клиенту отправлялся полностью готовый к отображению HTML-документ. 

Этот подход обеспечил быструю разработку и SEO-дружественность изначальной версии. Но со временем вылезли недостатки, характерные для «монолитных» приложений: низкая интерактивность на стороне клиента и необходимость постоянной перезагрузки страницы при любом взаимодействии с пользователем. Еще и сложность разработки и поддержки на стороне сервера постоянно росла.

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

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

Для более наглядной картины отобразим также и метрики. Метрики дают объективные данные для сравнения разных подходов и помогают ответить на ключевые вопросы: насколько быстро пользователь увидит основной контент страницы и сколько нужно времени, чтобы страница стала отзывчивой. Для описания метрик будем опираться на параметры Core Web Vitals — стандарт от Google. Однако, здесь возникает нюанс — метрик много и не все они в данном случае будут полезны. Так на какие нам следует обратить внимание? Определим некоторые из них:

  1. TTFB (Time to First Byte) — это время от момента запроса до получения первого байта данных от сервера. Почему оно для нас важно? Эта метрика показывает отзывчивость бэкенда. В случае классического SSR TTFB напрямую зависит от скорости работы базы данных и сложности шаблона. В гибридной версии TTFB будет минимален, так как сервер отдает статичный “скелет” страницы, не дожидаясь сбора всех данных.

  2. FCP (First Contentful Paint) — это время, за которое браузер отрисовывает первый значимый элемент (текст, изображение или логотип). Эта метрика в какой-то мере является эвристической, поскольку она представляет для пользователя момент, когда пользователь понимает: «Сайт работает, страница загружается, и мне не нужно на всякий случай перезагружать страницу».

  3. LCP (Largest Contentful Paint) — это время отрисовки самого крупного видимого элемента в области просмотра. Данная метрика считается основным показателем скорости загрузки страницы в глазах пользователя.

  4. CLS (Cumulative Layout Shift) — это метрика, измеряющая визуальную стабильность страницы. Она фиксирует все неожиданные «прыжки» контента. Высокий показатель CLS раздражает: вы собирались нажать на одну кнопку, но в этот момент сверху подгрузился блок, все уехало вниз, а вы в итоге нажали на рекламу.

Так что же с нашими показателями? Для режима SSR имеем значение TTFB, равное 38,57 мс:

Остальные показатели для полностью серверного рендеринга:

  • FCP — 0,5с,

  • LCP — 0,5с,

  • CLS — 0.

Смена парадигмы: переход к гибридной модели рендеринга

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

Основой нового подхода стал кастомный рендерер, который функционирует по следующему алгоритму:

  1. Начальная загрузка. Клиент при первом обращении получает от сервера не готовую HTML-страницу, а специальный шаблон (скелет) в формате, пригодном для последующей обработки (например, содержащий плейсхолдеры и инструкции). 

  2. Запрос данных. Клиентский код анализирует шаблон, определяет набор ключевых (якорных) переменных, необходимых для его заполнения, и отправляет их на сервер через REST API. 

  3. Обработка на сервере. Сервер, получив запрос, исполняет необходимую бизнес-логику, извлекает требуемые данные из БД и формирует JSON-объект, содержащий всю пользовательскую информацию. 

  4. Финальный рендеринг на клиенте. Полученный JSON-объект передается в тот же кастомный рендерер. Рендерер выполняет подстановку данных в шаблон и генерирует итоговый HTML-код, который затем отображается в браузере.

Перейдем на страницу с гибридным подходом:

Как видно из рисунка выше, при загрузке страницы уже выполняется несколько запросов. Первый в списке — это запрос для получения «скелета» страницы, в который в последующем будут вставляться данные. Затем идут два запроса на получение JavaScript-файлов с логикой сбора и загрузки якорных переменных — по сути, данных, которые мы запрашиваем у сервера. Последний в списке запрос — это как раз-таки запрос на получение данных по якорным переменным. Здесь наш кастомный сборщик собирает список переменных и отправляет их на сервер. Сервер, в свою очередь, присылает нам необходимые значения, которые кастомный скрипт подставляет.

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

Значение TTFB:

Как мы можем видеть, значение TTFB в данном подходе — 8,11 мс. Это кратно меньше, чем в случае с SSR. Это логично, поскольку TTFB показывает скорость загрузки «первого байта», и в данном случае наш «скелет» страницы загружается быстрее, чем весь предзаполненный документ в случае с SSR.


Остальные метрики:

  • FCP — 0,2с,

  • LCP — 0,4с,

  • CLS — 0.

Однако, если смотреть на параметр CLS, который отвечает за «сдвиг» контента при отрисовке (а не в момент создания отчета Lighthouse, когда данные еще не пришли и не отображались на странице), то значение, которое можно найти на вкладке Performance, будет отличаться от того, что показывает Lighthouse:

Как мы видим, данный параметр превышает нормальные значения (> 0,1 с). Это вызвано тем, что в случае гибридной модели загрузки контента часть страницы может отображаться позже и влиять на уже сформированную разметку.

Облачная инфраструктура для ваших проектов

Виртуальные машины в Москве, Санкт-Петербурге и Новосибирске с оплатой по потреблению.

Подробнее →

Испытание нестабильным соединением

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

Для демонстрации попробуем загрузить страницы обоих подходов в условиях ограниченного 3G-соединения. Для этого выберем в DevTools соответствующий режим и откроем каждую из страниц. Начнем со страницы, которая реализована при помощи SSR:

Как видно из рисунка, скорость загрузки страницы выросла с нескольких десятков миллисекунд до ~8.5 секунд. При этом в процессе загрузки страницы пользователь видит только белый экран браузера.

Проведем аналогичный эксперимент для страницы, реализованной через гибридный подход:

Как мы можем заметить, загрузка начального «скелета» шаблона (TTFB-метрика) заняла всего 2 с небольшим секунды. Понятно, что это страница с заглушками вместо данных, а настоящие данные все еще загружаются, но при этом пользователь имеет понимание, что страница не «упала», а загружается и он может с ней взаимодействовать.

Время загрузки клиентских скриптов и время выполнения запроса для получения необходимых данных показано ниже в таблице: 

Название ресурса

Тип (MIME)

Время (мс)

hybrid/

text/html

2132.20

context_collector.js

text/javascript

2038.62

context_render.js

text/javascript

2160.67

context/?scope=[...]

application/json

3659.74

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

Заключение

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

Проведенный нами эксперимент наглядно показал: там, где Server Side Rendering обеспечивает монолитную стабильность макета и отсутствие сдвигов страницы впоследствии, он неизбежно проигрывает в скорости отклика при работе с тяжелыми данными либо в условиях медленного сетевого соединения. В то же время гибридный подход доказал свою эффективность в «борьбе за внимание» пользователя: использование асинхронной загрузки данных и наличие прелоадеров позволило сократить параметр FCP до минимума, создавая ощущение мгновенной работы даже при медленном интернет-соединении.

Резюмируя все сказанное, можно заключить: не существует «серебрянной пули», единственно верного способа рендерить приложение. Нельзя взять модный фреймворк и надеяться, что он решит за нас все проблемы.

Выбор стратегии — это всегда компромисс. При проектировании каждого приложения необходимо внимательно учитывать все факторы:

  • с каким видом контента будет работать приложение,

  • на какую платформу оно ориентировано,

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

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