Рендеринг в Интернете

Где нам следует реализовать логику и рендеринг в наших приложениях? Должны ли мы использовать рендеринг на стороне сервера? А как насчет регидратации? Давайте найдем ответы!

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

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

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

Терминология

Рендеринг

  • Рендеринг на стороне сервера (SSR): рендеринг клиентского или универсального приложения в HTML на сервере.
  • Рендеринг на стороне клиента (CSR): рендеринг приложения в браузере с помощью JavaScript для изменения DOM.
  • Регидратация: «загрузка» представлений JavaScript на клиенте таким образом, чтобы они повторно использовали дерево и данные DOM HTML, отображаемые на сервере.
  • Предварительный рендеринг: запуск клиентского приложения во время сборки для захвата его исходного состояния в виде статического HTML.

Производительность

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

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

Рендеринг на стороне сервера обычно обеспечивает быстрый FCP. Запуск логики страницы и рендеринг на сервере позволяет избежать отправки большого количества JavaScript клиенту. Это помогает снизить TBT страницы, что также может привести к снижению INP, поскольку основной поток не блокируется так часто во время загрузки страницы. Когда основной поток блокируется реже, взаимодействие с пользователем будет иметь больше возможностей для более быстрого выполнения. Это имеет смысл, поскольку при рендеринге на стороне сервера вы просто отправляете текст и ссылки в браузер пользователя. Этот подход может хорошо работать для широкого спектра устройств и сетевых условий и открывает интересные возможности оптимизации браузера, такие как потоковый анализ документов.

Диаграмма, показывающая рендеринг на стороне сервера и выполнение JS, влияющее на FCP и TTI.

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

Достаточно ли для вашего приложения рендеринга на стороне сервера, во многом зависит от того, какой тип взаимодействия вы создаете. Существует давняя дискуссия о правильности применения рендеринга на стороне сервера по сравнению с рендерингом на стороне клиента, но важно помнить, что вы можете выбрать использование рендеринга на стороне сервера для некоторых страниц, а не для других. Некоторые сайты успешно применяют гибридные методы рендеринга. Сервер Netflix отображает свои относительно статичные целевые страницы, предварительно загружая JS для страниц с большим количеством взаимодействий, что дает этим более тяжелым страницам, отображаемым клиентом, больше шансов на быструю загрузку.

Многие современные платформы, библиотеки и архитектуры позволяют отображать одно и то же приложение как на клиенте, так и на сервере. Эти методы можно использовать для рендеринга на стороне сервера. Однако важно отметить, что архитектуры, в которых рендеринг происходит как на сервере , так и на клиенте, представляют собой отдельный класс решений с совершенно разными характеристиками производительности и компромиссами. Пользователи React могут использовать серверные API-интерфейсы DOM или решения, созданные на их основе, такие как Next.js, для рендеринга на стороне сервера. Пользователи Vue могут просмотреть руководство по рендерингу на стороне сервера Vue или Nuxt . У Angular есть Universal . Однако в большинстве популярных решений используется та или иная форма гидратации, поэтому перед выбором инструмента ознакомьтесь с используемым подходом.

Статический рендеринг

Статический рендеринг происходит во время сборки. Этот подход предлагает быстрый FCP, а также меньшие TBT и INP — при условии, что количество JS на стороне клиента ограничено. В отличие от рендеринга на стороне сервера, здесь также удается добиться стабильно быстрого TTFB, поскольку HTML для страницы не обязательно динамически генерируется на сервере. Как правило, статическая отрисовка означает предварительное создание отдельного HTML-файла для каждого URL-адреса. Благодаря заранее сгенерированным HTML-ответам статические рендеринги можно развернуть на нескольких CDN, чтобы воспользоваться преимуществами пограничного кэширования.

Диаграмма, показывающая статический рендеринг и дополнительное выполнение JS, влияющее на FCP и TTI.

Решения для статического рендеринга бывают самых разных форм и размеров. Такие инструменты, как Gatsby , созданы для того, чтобы дать разработчикам ощущение, будто их приложение визуализируется динамически, а не генерируется на этапе сборки. Инструменты создания статических сайтов, такие как 11ty , Jekyll и Metalsmith , учитывают свою статическую природу, обеспечивая подход, в большей степени основанный на шаблонах.

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

Пользователи React могут быть знакомы с Gatsby, статическим экспортом Next.js или Navi — все это упрощает создание страниц с использованием компонентов. Однако важно понимать разницу между статическим рендерингом и предварительным рендерингом: статические визуализированные страницы являются интерактивными без необходимости выполнения большого количества клиентского JavaScript, тогда как предварительный рендеринг улучшает FCP одностраничного приложения, которое должно быть загружено на клиенте, чтобы страницы должны быть по-настоящему интерактивными.

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

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

Рендеринг на стороне сервера и статический рендеринг

Рендеринг на стороне сервера не является панацеей: его динамический характер может повлечь за собой значительные накладные расходы на вычисления. Многие решения рендеринга на стороне сервера не сбрасывают данные заранее, могут задерживать TTFB или удваивать отправляемые данные (например, встроенное состояние, используемое JavaScript на клиенте). В React метод renderToString() может работать медленно, поскольку он синхронный и однопоточный. Новые API-интерфейсы DOM сервера React, поддерживающие потоковую передачу, которые могут быстрее передать начальную часть HTML-ответа браузеру, в то время как остальная часть все еще генерируется на сервере.

Чтобы добиться «правильного» рендеринга на стороне сервера, может потребоваться поиск или создание решения для кэширования компонентов , управления потреблением памяти, применения методов мемоизации и других проблем. Обычно вы обрабатываете/перестраиваете одно и то же приложение несколько раз — один раз на клиенте и один раз на сервере. Тот факт, что рендеринг на стороне сервера может заставить что-то отображаться раньше, не означает, что у вас внезапно становится меньше работы — если у вас много работы на клиенте после того, как на клиент приходит сгенерированный сервером HTML-ответ, это все равно может привести к к более высокому TBT и INP для вашего веб-сайта.

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

Рендеринг на стороне сервера также может представлять собой интересные решения при создании PWA : лучше ли использовать полностраничное кэширование сервис-воркеров или просто рендерить на сервере отдельные фрагменты контента?

Рендеринг на стороне клиента

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

Рендеринг на стороне клиента может быть сложно обеспечить и обеспечить быстроту для мобильных устройств. Если выполнить минимальную работу, рендеринг на стороне клиента может приблизиться к производительности чистого рендеринга на стороне сервера, сохраняя ограниченный бюджет JavaScript и обеспечивая максимальную эффективность за минимальное количество циклов обработки . Критические сценарии и данные могут быть доставлены быстрее с помощью <link rel=preload> , что позволит синтаксическому анализатору работать быстрее. Такие шаблоны, как PRPL, также стоит оценить, чтобы обеспечить мгновенную начальную и последующую навигацию.

Диаграмма, показывающая влияние рендеринга на стороне клиента на FCP и TTI.

Основным недостатком рендеринга на стороне клиента является то, что объем требуемого JavaScript имеет тенденцию расти по мере роста приложения, что может отрицательно повлиять на INP страницы. Это становится особенно трудным с добавлением новых библиотек JavaScript, полифилов и стороннего кода, которые конкурируют за вычислительную мощность и часто должны обрабатываться перед отображением содержимого страницы.

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

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

Объединение рендеринга на стороне сервера и рендеринга на стороне клиента посредством регидратации.

Этот подход пытается сгладить компромисс между рендерингом на стороне клиента и рендерингом на стороне сервера, используя оба варианта. Запросы навигации, такие как полная загрузка или перезагрузка страницы, обрабатываются сервером, который отображает приложение в HTML, затем JavaScript и данные, используемые для рендеринга, внедряются в результирующий документ. Если все сделано осторожно, это обеспечивает быстрый FCP, такой же, как рендеринг на стороне сервера, а затем «набирает обороты» путем повторного рендеринга на клиенте с использованием метода, называемого (ре)гидратацией . Это эффективное решение, но оно может иметь значительные недостатки в производительности.

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

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

Проблема регидратации: одно приложение по цене двух

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

HTML-документ, содержащий сериализованный пользовательский интерфейс, встроенные данные и скрипт Bundle.js.

Как видите, сервер возвращает описание пользовательского интерфейса приложения в ответ на запрос навигации, а также исходные данные, использованные для создания этого пользовательского интерфейса, и полную копию реализации пользовательского интерфейса, которая затем загружается на клиенте. . Только после завершения загрузки и выполнения bundle.js этот пользовательский интерфейс становится интерактивным.

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

Диаграмма, показывающая негативное влияние клиентского рендеринга на TTI.

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

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

За последние несколько лет серверный рендеринг претерпел ряд изменений.

Потоковая отрисовка на стороне сервера позволяет отправлять HTML-код частями, которые браузер может постепенно отображать по мере его получения. Это может привести к более быстрому FCP, поскольку разметка доходит до пользователей быстрее. В React асинхронность потоков в [ renderToPipeableStream() ] — по сравнению с синхронным renderToString() означает, что противодавление обрабатывается хорошо.

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

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

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

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

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

Тризоморфный рендеринг

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

Схема тризоморфного рендеринга, показывающая браузер и сервис-работника, взаимодействующих с сервером.

SEO-соображения

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

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

Снимок экрана пользовательского интерфейса теста совместимости с мобильными устройствами.

Подведение итогов

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

Инфографика, показывающая спектр опций, описанных в этой статье.

Кредиты

Спасибо всем за отзывы и вдохновение:

Джеффри Посник, Хусейн Джирде, Шубхи Паникер, Крис Харрельсон и Себастьян Маркбоге