Одно из основных решений, которые должны принять веб-разработчики, — это где реализовать логику и рендеринг в их приложении. Это может быть сложно, поскольку существует множество способов создать веб-сайт.
Наше понимание этой области определяется нашей работой в Chrome и общением с крупными сайтами за последние несколько лет. В общих чертах, мы рекомендуем разработчикам рассмотреть возможность рендеринга на стороне сервера или статического рендеринга вместо подхода полной регидратации.
Чтобы лучше понять архитектуру, которую мы выбираем, когда принимаем это решение, нам необходимо четкое понимание каждого подхода и последовательная терминология, которую можно использовать, говоря о них. Различия между подходами к рендерингу помогают проиллюстрировать компромиссы рендеринга в Интернете с точки зрения производительности страницы.
Терминология
Во-первых, мы определим некоторую терминологию, которую будем использовать.
Рендеринг
- Серверный рендеринг (SSR)
- Рендеринг приложения на сервере для отправки клиенту HTML, а не JavaScript.
- Рендеринг на стороне клиента (CSR)
- Рендеринг приложения в браузере с использованием JavaScript для изменения DOM.
- Регидратация
- «Загрузка» представлений JavaScript на клиенте, чтобы они повторно использовали дерево и данные DOM HTML, отображаемые на сервере.
- Предварительный рендеринг
- Запуск клиентского приложения во время сборки для фиксации его исходного состояния в виде статического HTML.
Производительность
- Время до первого байта (TTFB)
- Время между нажатием ссылки и загрузкой первого байта контента на новой странице.
- Первая содержательная краска (FCP)
- Время, когда запрашиваемый контент (тело статьи и т. д.) становится видимым.
- Взаимодействие со следующей отрисовкой (INP)
- Репрезентативный показатель, который оценивает, быстро ли страница реагирует на действия пользователя.
- Общее время блокировки (TBT)
- Прокси-метрика для INP , которая рассчитывает, как долго основной поток был заблокирован во время загрузки страницы.
Серверный рендеринг
Серверный рендеринг генерирует полный HTML-код страницы на сервере в ответ на навигацию. Это позволяет избежать дополнительных циклических операций по выборке данных и созданию шаблонов на клиенте, поскольку средство визуализации обрабатывает их до того, как браузер получит ответ.
Рендеринг на стороне сервера обычно обеспечивает быстрый FCP. Запуск логики страницы и ее рендеринг на сервере позволяют избежать отправки большого количества кода JavaScript клиенту. Это помогает снизить TBT страницы, что также может привести к снижению INP, поскольку основной поток не блокируется так часто во время загрузки страницы. Когда основной поток блокируется реже, взаимодействие с пользователем имеет больше возможностей для более быстрого выполнения. Это имеет смысл, поскольку при рендеринге на стороне сервера вы просто отправляете текст и ссылки в браузер пользователя. Этот подход может хорошо работать для различных устройств и сетевых условий и открывает интересные возможности оптимизации браузера, такие как потоковый анализ документов.
При рендеринге на стороне сервера пользователям с меньшей вероятностью придется ждать запуска JavaScript, привязанного к процессору, прежде чем они смогут использовать ваш сайт. Даже если вы не можете избежать использования стороннего JS , использование рендеринга на стороне сервера для снижения затрат на собственный JavaScript может дать вам больше бюджета на все остальное. Однако у этого подхода есть один потенциальный компромисс: создание страниц на сервере требует времени, что может увеличить TTFB вашей страницы.
Достаточно ли серверного рендеринга для вашего приложения, во многом зависит от того, какой тип взаимодействия вы создаете. Существует давняя дискуссия о правильности применения рендеринга на стороне сервера по сравнению с рендерингом на стороне клиента, но вы всегда можете выбрать использование рендеринга на стороне сервера для некоторых страниц, а не для других. Некоторые сайты успешно применяют гибридные методы рендеринга. Например, сервер Netflix отображает свои относительно статичные целевые страницы, предварительно загружая JS для страниц с большим количеством взаимодействий, что дает этим более тяжелым страницам, отображаемым клиентом, больше шансов на быструю загрузку.
Многие современные платформы, библиотеки и архитектуры позволяют отображать одно и то же приложение как на клиенте, так и на сервере. Вы можете использовать эти методы для рендеринга на стороне сервера. Однако архитектуры, в которых рендеринг происходит как на сервере , так и на клиенте, представляют собой отдельный класс решений с совершенно разными характеристиками производительности и компромиссами. Пользователи React могут использовать серверные API-интерфейсы DOM или решения, созданные на их основе, такие как Next.js, для рендеринга на стороне сервера. Пользователи Vue могут использовать руководство по рендерингу на стороне сервера или Nuxt . У Angular есть Universal . Однако в большинстве популярных решений используется та или иная форма гидратации, поэтому помните о подходах, которые использует ваш инструмент.
Статический рендеринг
Статический рендеринг происходит во время сборки. Этот подход предлагает быстрый FCP, а также более низкие TBT и INP, если вы ограничиваете количество клиентского JS на своих страницах. В отличие от рендеринга на стороне сервера, он также обеспечивает стабильно быстрый TTFB, поскольку HTML для страницы не обязательно динамически генерируется на сервере. Как правило, статическая отрисовка означает предварительное создание отдельного HTML-файла для каждого URL-адреса. Сгенерировав заранее HTML-ответы, вы можете развернуть статические рендеринги на нескольких CDN, чтобы воспользоваться преимуществами пограничного кэширования.
Решения для статического рендеринга бывают самых разных форм и размеров. Такие инструменты, как Gatsby, созданы для того, чтобы дать разработчикам ощущение, будто их приложение визуализируется динамически, а не генерируется на этапе сборки. Инструменты создания статических сайтов, такие как 11ty , Jekyll и Metalsmith, учитывают свою статическую природу, обеспечивая подход, в большей степени основанный на шаблонах.
Одним из недостатков статического рендеринга является то, что он должен генерировать отдельные HTML-файлы для каждого возможного URL-адреса. Это может быть сложно или даже невозможно, если вы не можете заранее предсказать, какими будут эти URL-адреса, или для сайтов с большим количеством уникальных страниц.
Пользователи React могут быть знакомы с Gatsby, статическим экспортом Next.js или Navi , которые позволяют удобно создавать страницы из компонентов. Однако статический рендеринг и предварительный рендеринг ведут себя по-разному: статически визуализированные страницы являются интерактивными без необходимости выполнения большого количества клиентского JavaScript, тогда как предварительный рендеринг улучшает FCP одностраничного приложения, которое необходимо загрузить на клиенте, чтобы сделать страницы по-настоящему интерактивными.
Если вы не уверены, является ли данное решение статическим или предварительным рендерингом, попробуйте отключить JavaScript и загрузить страницу, которую хотите протестировать. Для статически отображаемых страниц большинство интерактивных функций по-прежнему существуют без 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, чтобы обеспечить мгновенную начальную и последующую навигацию.
Основным недостатком рендеринга на стороне клиента является то, что объем требуемого JavaScript имеет тенденцию расти по мере роста приложения, что может повлиять на INP страницы. Это становится особенно сложно с добавлением новых библиотек JavaScript, полифилов и стороннего кода, которые конкурируют за вычислительную мощность и часто должны быть обработаны, прежде чем содержимое страницы сможет отображаться.
В приложениях, которые используют рендеринг на стороне клиента и полагаются на большие пакеты JavaScript, следует учитывать агрессивное разделение кода для снижения TBT и INP во время загрузки страницы, а также ленивую загрузку JavaScript, чтобы предоставлять только то, что нужно пользователю, когда это необходимо. Для приложений с небольшой интерактивностью или ее отсутствием рендеринг на стороне сервера может представлять собой более масштабируемое решение этих проблем.
Для людей, создающих одностраничные приложения, идентификация основных частей пользовательского интерфейса, общих для большинства страниц, позволяет применить технику кэширования оболочки приложения . В сочетании с сервис-воркерами это может значительно повысить воспринимаемую производительность при повторных посещениях, поскольку страница может очень быстро загружать HTML-код оболочки приложения и зависимости из CacheStorage
.
Регидратация сочетает в себе рендеринг на стороне сервера и на стороне клиента.
Регидратация — это подход, который пытается сгладить компромисс между рендерингом на стороне клиента и на стороне сервера, выполняя оба действия. Запросы навигации, такие как полная загрузка или перезагрузка страницы, обрабатываются сервером, который отображает приложение в HTML, затем JavaScript и данные, используемые для рендеринга, внедряются в результирующий документ. Если все сделано осторожно, это обеспечивает быстрый FCP-рендеринг, подобный рендерингу на стороне сервера, а затем «подхватывает» его повторным рендерингом на клиенте. Это эффективное решение, но оно может иметь значительные недостатки в производительности.
Основным недостатком рендеринга на стороне сервера с регидратацией является то, что он может оказать существенное негативное влияние на TBT и INP, даже если он улучшает FCP. Страницы, отображаемые на стороне сервера, могут выглядеть загруженными и интерактивными, но на самом деле они не могут реагировать на вводимые данные до тех пор, пока не будут выполнены клиентские сценарии для компонентов и не будут подключены обработчики событий. На мобильных устройствах это может занять несколько минут, сбивая и расстраивая пользователя.
Проблема регидратации: одно приложение по цене двух
Чтобы клиентский JavaScript точно «продолжал» с того места, на котором остановился сервер, без повторного запроса всех данных, с помощью которых сервер визуализировал свой HTML, большинство серверных решений рендеринга сериализуют ответ из зависимостей данных пользовательского интерфейса в виде тегов сценария в документ. Поскольку это дублирует большую часть HTML, регидратация может вызвать больше проблем, чем просто задержка интерактивности.
Сервер возвращает описание пользовательского интерфейса приложения в ответ на запрос навигации, а также исходные данные, использованные для создания этого пользовательского интерфейса, и полную копию реализации пользовательского интерфейса, которая затем загружается на клиенте. Пользовательский интерфейс не станет интерактивным до тех пор, пока bundle.js
не завершит загрузку и выполнение.
Показатели производительности, собранные на реальных веб-сайтах с использованием рендеринга и регидратации на стороне сервера, показывают, что это редко бывает лучшим вариантом. Самая важная причина — это влияние на пользовательский опыт: страница выглядит готовой, но ни одна из ее интерактивных функций не работает.
Однако есть надежда на рендеринг на стороне сервера с регидратацией. В краткосрочной перспективе использование рендеринга на стороне сервера только для контента с высокой степенью кэшируемости может уменьшить TTFB, давая результаты, аналогичные предварительному рендерингу. Постепенная , постепенная или частичная регидратация может стать ключом к тому, чтобы сделать этот метод более жизнеспособным в будущем.
Потоковое рендеринг на стороне сервера и постепенная регидратация
За последние несколько лет серверный рендеринг претерпел ряд изменений.
Потоковая отрисовка на стороне сервера позволяет отправлять HTML-код частями, которые браузер может постепенно отображать по мере его получения. Это может ускорить передачу разметки вашим пользователям, ускоряя работу FCP. В React асинхронность потоков в renderToPipeableStream()
по сравнению с синхронным renderToString()
означает, что противодавление обрабатывается хорошо.
Также стоит учитывать прогрессивную регидратацию, и React ее реализовал . При таком подходе отдельные части приложения, отображаемого на сервере, «загружаются» с течением времени, вместо обычного подхода, заключающегося в одновременной инициализации всего приложения. Это может помочь уменьшить объем JavaScript, необходимого для интерактивности страниц, поскольку позволяет отложить обновление на стороне клиента частей страницы с низким приоритетом, чтобы предотвратить блокировку основного потока, позволяя взаимодействиям с пользователем происходить раньше после того, как пользователь их инициирует. .
Прогрессивная регидратация также может помочь вам избежать одной из наиболее распространенных ошибок регидратации рендеринга на стороне сервера: дерево DOM, отображаемое на сервере, уничтожается, а затем немедленно перестраивается, чаще всего потому, что для первоначального синхронного рендеринга на стороне клиента требовались данные, которые были не совсем готовы. , часто это Promise
, который еще не решен.
Частичная регидратация
Частичную регидратацию оказалось трудно осуществить. Этот подход является расширением прогрессивной регидратации, которая анализирует отдельные части страницы (компоненты, представления или деревья) и идентифицирует части с небольшой интерактивностью или без реактивности. Для каждой из этих, в основном, статических частей соответствующий код JavaScript затем преобразуется в инертные ссылки и декоративные элементы, сводя их нагрузку на стороне клиента почти до нуля.
Подход частичной гидратации имеет свои проблемы и компромиссы. Это создает некоторые интересные проблемы для кэширования, а навигация на стороне клиента означает, что мы не можем предполагать, что HTML-код, отображаемый на сервере, для инертных частей приложения доступен без полной загрузки страницы.
Тризоморфный рендеринг
Если вам подходят сервисные работники , рассмотрите тризоморфный рендеринг. Это метод, который позволяет вам использовать потоковую отрисовку на стороне сервера для начальной навигации или навигации без использования JS, а затем поручить вашему сервисному работнику рендеринг HTML для навигации после его установки. Это может поддерживать актуальность кэшированных компонентов и шаблонов и обеспечивать навигацию в стиле SPA для рендеринга новых представлений в одном сеансе. Этот подход работает лучше всего, когда вы можете использовать один и тот же код шаблона и маршрутизации между сервером, клиентской страницей и сервис-воркером.
SEO-соображения
При выборе стратегии веб-рендеринга команды часто учитывают влияние SEO. Рендеринг на стороне сервера – популярный выбор для обеспечения "полного" представления, которое могут интерпретировать сканеры. Сканеры могут понимать JavaScript , но часто существуют ограничения на то, как они визуализируются. Рендеринг на стороне клиента может работать, но часто требует дополнительного тестирования и дополнительных затрат. Совсем недавно динамический рендеринг также стал вариантом, который стоит рассмотреть, если ваша архитектура сильно зависит от клиентского JavaScript.
Если вы сомневаетесь, инструмент тестирования, оптимизированный для мобильных устройств, — отличный способ проверить, соответствует ли выбранный вами подход тому, на что вы надеетесь. Он показывает визуальный предварительный просмотр того, как любая страница выглядит для сканера Google, сериализованный HTML-контент, который он находит после выполнения JavaScript, а также любые ошибки, возникшие во время рендеринга.
Заключение
При выборе подхода к рендерингу измерьте и поймите, какие у вас узкие места. Подумайте, поможет ли вам добиться большей цели статический рендеринг или рендеринг на стороне сервера. В основном можно отправлять HTML с минимальным количеством JavaScript, чтобы обеспечить интерактивность. Вот удобная инфографика, показывающая спектр сервер-клиент:
Кредиты
Спасибо всем за отзывы и вдохновение:
Джеффри Посник, Хусейн Джирде, Шубхи Паникер, Крис Харрельсон и Себастьян Маркбоге