Клиентский рендеринг HTML и интерактивность

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

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

Однако разработчики могут обходить настройки браузера по умолчанию в соответствии с потребностями своих приложений. Это, безусловно, относится к веб-сайтам, использующим шаблон одностраничного приложения (SPA) , который динамически создаёт значительные фрагменты HTML/DOM на клиентской стороне с помощью JavaScript. Этот шаблон проектирования называется рендерингом на стороне клиента, и он может повлиять на взаимодействие до следующей отрисовки (INP) вашего веб-сайта, если объём выполняемых действий слишком велик.

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

Как браузер отображает HTML, предоставленный сервером

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

  1. Браузер отправляет навигационный запрос по предоставленному URL-адресу.
  2. Сервер отвечает фрагментами HTML.

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

Скриншот парсинга HTML-кода, отправленного сервером, представленный на панели производительности Chrome DevTools. По мере поступления HTML-кода его фрагменты обрабатываются в рамках нескольких коротких задач, и рендеринг выполняется поэтапно.
Анализ и рендеринг HTML-кода, предоставляемого сервером, отображаются на панели производительности Chrome DevTools. Задачи, связанные с анализом и рендерингом HTML-кода, разделены на этапы.

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

Вывод? При потоковой передаче HTML с сервера вы получаете инкрементальный парсинг и рендеринг HTML, а также автоматическую передачу основного потока бесплатно. При рендеринге на стороне клиента этого не происходит.

Как браузер отображает HTML, предоставленный JavaScript

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

Клиентская визуализация может также происходить в не-SPA-приложениях в более редких случаях, когда HTML динамически добавляется в DOM посредством JavaScript.

Существует несколько распространенных способов создания HTML или добавления в DOM с помощью JavaScript:

  1. Свойство innerHTML позволяет вам задать содержимое существующего элемента с помощью строки, которую браузер анализирует в DOM.
  2. Метод document.createElement позволяет создавать новые элементы для добавления в DOM без использования HTML-анализа браузером.
  3. Метод document.write позволяет записать HTML-код в документ (и браузер его анализирует, как в подходе №1). Однако по ряду причин использование document.write настоятельно не рекомендуется.
Скриншот парсинга HTML-кода, отрисованного с помощью JavaScript, на панели производительности в Chrome DevTools. Работа выполняется в рамках одной длительной задачи, которая блокирует основной поток.
Парсинг и рендеринг HTML с помощью JavaScript на клиенте, как показано на панели производительности в Chrome DevTools. Задачи, связанные с парсингом и рендерингом, не разбиты на части, что приводит к длительному выполнению задачи, блокирующему основной поток.

Последствия создания HTML/DOM с помощью клиентского JavaScript могут быть значительными:

  • В отличие от HTML-кода, передаваемого сервером в ответ на навигационный запрос, задачи JavaScript на клиенте не разбиваются автоматически на фрагменты, что может привести к длительному выполнению задач, блокирующему основной поток. Это означает, что INP вашей страницы может пострадать, если вы одновременно создаёте слишком много HTML/DOM-кода на клиенте.
  • Если HTML-код создаётся на клиенте во время запуска, ресурсы, на которые он ссылается , не будут обнаружены сканером предварительной загрузки браузера . Это, безусловно, негативно скажется на отрисовке самого большого содержимого (LCP) страницы. Хотя это не проблема производительности во время выполнения (а проблема сетевой задержки при загрузке важных ресурсов), вы не хотите, чтобы LCP вашего сайта пострадал из-за игнорирования этой фундаментальной оптимизации производительности браузера.

Что можно сделать с влиянием клиентского рендеринга на производительность

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

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

Предоставьте как можно больше HTML-кода с сервера

Как упоминалось ранее, браузер по умолчанию обрабатывает HTML с сервера очень эффективно. Он разбивает парсинг и рендеринг HTML, избегая длительных задач, и оптимизирует общее время основного потока. Это приводит к снижению общего времени блокировки (TBT) , которое сильно коррелирует с INP .

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

  • Для React вам нужно будет использовать API Server DOM для рендеринга HTML на сервере. Но имейте в виду: традиционный метод рендеринга на стороне сервера использует синхронный подход , что может привести к увеличению времени до первого байта (TTFB) , а также последующих метрик, таких как первая отрисовка содержимого (FCP) и LCP. По возможности убедитесь, что вы используете потоковые API для Node.js или других сред выполнения JavaScript , чтобы сервер мог начать потоковую передачу HTML в браузер как можно скорее. Next.js — фреймворк на основе React — предоставляет множество рекомендаций по умолчанию. Помимо автоматического рендеринга HTML на сервере, он также может статически генерировать HTML для страниц, которые не меняются в зависимости от контекста пользователя (например, аутентификации).
  • Vue также по умолчанию выполняет рендеринг на стороне клиента. Однако, как и React, Vue может также рендерить HTML-код компонента на сервере . Используйте эти серверные API, где это возможно, или рассмотрите возможность использования более высокоуровневой абстракции для вашего проекта Vue, чтобы упростить реализацию передовых методов.
  • Svelte по умолчанию отображает HTML на сервере , однако, если коду вашего компонента требуется доступ к пространствам имён, доступным только в браузере (например, window ), вы можете не иметь возможности отобразить HTML этого компонента на сервере. По возможности изучите альтернативные подходы, чтобы избежать ненужной отрисовки на стороне клиента. SvelteKit , который для Svelte — то же самое, что Next.js для React, максимально внедряет множество передовых практик в ваши проекты Svelte, что позволяет избежать потенциальных проблем в проектах, использующих только Svelte.

Ограничить количество узлов DOM, создаваемых на клиенте

Чем больше DOM-объекты, тем больше вычислительных ресурсов требуется для их отображения. Независимо от того, представляет ли ваш сайт полноценный SPA-сайт или внедряет новые узлы в существующий DOM в результате взаимодействия с MPA-объектом, постарайтесь сделать эти DOM-объекты как можно меньше. Это поможет сократить объем работы, требуемый при клиентском рендеринге HTML-кода, и, как ожидается, снизить INP вашего сайта.

Рассмотрим архитектуру потокового сервиса-работника

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

При успешном использовании этого подхода HTML-код не создаётся на клиенте, но мгновенная загрузка фрагментов контента из кэша создаёт впечатление быстрой загрузки сайта. Веб-сайты, использующие этот подход, могут выглядеть почти как SPA, но без недостатков клиентского рендеринга. Кроме того, он сокращает объём HTML-кода, запрашиваемого с сервера .

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

Заключение

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

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

Если вы сможете сделать рендеринг вашего веб-сайта на стороне клиента как можно минималистичнее, вы улучшите не только INP вашего веб-сайта, но и другие показатели, такие как LCP, TBT и, возможно, в некоторых случаях даже ваш TTFB.

Главное изображение из Unsplash , автор Майк Джониетц .