что такое рендеринг страницы
Рендеринг веб сайтов 101
Вы вводите название сайта в адресную строку браузера, нажимаете enter, и по привычке видите запрашиваемую страницу. Все просто: ввел название сайта — сайт отобразился. Однако для более любознательных хочу рассказать, что происходит между тем как браузер начинает получать куски сайта (да, сайт приходит кусками, по-другому — чанками) и отображает полностью нарисованную страницу.
Как устроен браузер?
Перед историей о том, как браузер рисует страницу, важно понять как он устроен, какие процессы и на каком уровне выполняются. При знакомстве с процессом рендеринга мы не раз вспомним о компонентах браузера. Итак, под капотом браузер выглядит примерно следующим образом:
User Interface — это все что видит пользователь: адресная строка, кнопки вперед/назад, меню, закладки — за исключением области где отображается сайт.
Browser Engine отвечает за взаимодействие между User Interface и Rendering Engine. Например клик по кнопке назад должен сказать компоненте RE что нужно отрисовать предыдущее состояние.
Network выполняет xhr запросы за ресурсами, и в целом, общение браузера с остальным интернетом происходит через эту компоненту, включая проксирование, кэширование и так далее.
JS Engine место где парсится и исполняется js код.
UI Backend используется чтобы рисовать стандартные компоненты типа чекбоксов, инпутов, кнопок.
Data Persistence отвечает за хранение локальных данных, например в куках, SessionStorage, indexDB и так далее.
Далее узнаем как рассмотренные компоненты браузера взаимодействуют между собой и разберем подробнее, что происходит внутри Rendering Engine. Другими словами …
Как браузер переводит html в пиксели на экране?
Итак, с помощью компонента Network браузер начал получать html-файл чанками обычно по 8кб, что дальше? А далее идет процесс парсинга (спецификация процесса) и рендеринга этого файла в компоненте, как вы уже догадались — Rendering Engine.
Важно! Для повышения юзабилити, браузер не дожидается пока загрузится и распарсится весь html. Вместо этого браузер сразу пытается отобразить пользователю страницу (далее рассмотрим как).
Сам процесс парсинга выглядит так:
Результатом парсинга является DOM дерево. Возьмем к примеру такой html:
DOM дерево такого html файла будет выглядеть так:
Как работает JS: движки рендеринга веб-страниц и советы по оптимизации их производительности
Сегодня, в переводе одиннадцатой части серии материалов, посвящённых JavaScript, мы поговорим о подсистемах браузера, ответственных за рендеринг веб-страниц. Они играют ключевую роль в деле преобразования описаний документов, выполненных с помощью HTML и CSS, в то, что мы видим на экране.
Автор материала говорит, что в компании SessionStack приходится уделять рендерингу огромное внимание. В этой статье он поделится советами, касающимися оптимизации веб-страниц с учётом особенностей их визуализации.
Обзор
Создавая веб-приложения, мы не пишем изолированный JS-код, который занимается исключительно какими-то собственными «внутренними» делами. Этот код выполняется в окружении, предоставляемом ему браузером, взаимодействует с ним. Понимание устройства этого окружения, того, как оно работает, из каких частей состоит, позволяет разработчику создавать более качественные программы, даёт ему возможность предусмотреть возникновение возможных проблем с приложением, которое вышло в свет.
На рисунке ниже показаны основные компоненты браузера. Давайте поговорим о том, какую роль они играют в процессе обработки веб-страниц.
Основные компоненты браузера
О различных движках рендеринга
Главная задача движка рендеринга заключается в том, чтобы вывести запрошенную страницу в окне браузера. Движок может выводить HTML-документы, XML-документы, изображения. При использовании дополнительных плагинов движок может визуализировать и материалы других типов, например — PDF-документы.
Мы знаем, что существуют различные JS-движки, которые используют различные браузеры. То же самое справедливо и для движков рендеринга. Вот несколько популярных движков:
Процесс рендеринга веб-страницы
Движок рендеринга получает содержимое запрошенного документа от сетевого уровня браузера. Процесс рендеринга выглядит так, как показано на рисунке ниже.
Процесс рендеринга веб-страницы
Вот основные этапы этого процесса:
Создание дерева DOM
Первый этап работы движка рендеринга заключается в разборе HTML-документа и преобразовании того, что у него получилось, в узлы DOM, размещённые в дереве DOM. При этом веб-страница, которая представлена в виде HTML-кода, преобразуется в структуру, подобную той, которая показана на рисунке ниже.
Каждый элемент этого дерева, содержащий вложенные элементы, является для них родительским. Это справедливо для всех уровней вложенности.
Создание дерева CSSOM
Как и в случае с HTML, движку нужно конвертировать CSS в нечто, с чем может работать браузер — в CSSOM. В результате получается дерево CSSOM, представленное на следующем рисунке.
Кроме того, обратите внимание на то, что вышеприведённое дерево не является полным CSSOM-деревом. Тут показаны лишь стили, которые мы, в нашем CSS-файле, решили переопределить. У каждого браузера имеется стандартный набор стилей, применяемый по умолчанию, известный ещё как «стили пользовательского агента» (user agent styles). Именно результаты применения этих стилей можно видеть на странице, не имеющей связанных с ней CSS-правил. Наши же стили просто переопределяют некоторые из стандартных стилей браузера.
Создание дерева рендеринга
Инструкции о внешнем виде элементов, представленные в HTML, скомбинированные с информацией об их стилизации из дерева CSSOM, используются для формирования дерева рендеринга.
Что это такое? Это — дерево визуальных элементов, созданных в том порядке, в котором они будут выводиться на экран. Это — визуальное представление HTML-кода страницы, отражающее влияние соответствующих этой странице CSS-правил. Цель этого дерева заключается в том, чтобы обеспечить вывод элементов правильном порядке.
Узел дерева рендеринга известен в движке WebKit как «renderer» или «render object» (мы будем называть их «объектами рендеринга»).
Вот как будет выглядеть дерево рендеринга для деревьев DOM и CSSOM, показанных выше.
Вот общее описание действий браузера, выполняемых им при создании дерева рендеринга.
Формирование макета страницы
После того, как объект рендеринга создан и добавлен в дерево, ему пока ещё не назначены позиция и размер. Вычисление этих значений и называется формированием макета страницы.
Процесс формирования макета означает задание каждому узлу точной позиции, в которой он должен появиться на странице.
Визуализация дерева рендеринга
На данном этапе осуществляется обход дерева рендеринга и вызов методов paint() объектов рендеринга, которые и выполняют вывод графического представления объектов на экран.
Визуализация, или отрисовка, может быть глобальной или инкрементной (так же выполняется и формирование макета страницы).
Серверный или клиентский рендеринг на вебе: что лучше использовать у себя в проекте и почему
Авторизуйтесь
Серверный или клиентский рендеринг на вебе: что лучше использовать у себя в проекте и почему
Разработчикам часто приходится принимать решения, которые повлияют на всю архитектуру приложения. Веб-разработчикам важно выбрать правильное место для реализации логики и рендеринга приложения. Это может быть непросто, так как сайт можно создать разными путями.
При выборе подхода для рендеринга нужно понимать разницу между возможными вариантами, чтобы не прогадать с производительностью. Сегодня мы разберёмся, в чём заключается эта разница.
Терминология
Отображение:
Производительность:
Серверный рендеринг
При серверном рендеринге в ответ на запрос на сервере генерируется весь HTML-код страницы. Это исключает необходимость дополнительных запросов данных со стороны клиента, так как сервер берёт всю работу на себя, прежде чем отправить ответ.
Такой подход позволяет добиться быстрой первой отрисовки и первой содержательной отрисовки. Выполнение логики страницы и рендеринг на сервере позволяют избежать отправки клиенту большого количества JavaScript, что приводит к меньшему времени до интерактивности. И это логично, ведь при серверном рендеринге пользователю отсылаются только текст и ссылки. Этот подход хорошо сработает на широком диапазоне устройств и сетевых условий, а также откроет возможности для интересных браузерных оптимизаций вроде потокового парсинга документа.
При использовании серверного рендеринга пользователям не нужно ждать завершения работы JavaScript, отнимающего ресурсы процессора, прежде чем они смогут начать работать с сайтом. Даже если нельзя избежать использования стороннего JavaScript, серверный рендеринг позволяет уменьшить количество вашего собственного JavaScript и даёт больше «бюджета» для всего остального. Однако у этого подхода есть один существенный недостаток: формирование страницы на сервере занимает определённое время, что может привести к большему времени до первого байта.
Ответ на вопрос «достаточно ли серверного рендеринга для моего приложения?» зависит от того, что вы создаёте. Сообщество давно дискутирует на тему правильного применения серверного рендеринга против клиентского, но важно помнить, что для одних страниц использовать серверный рендеринг можно, а для других — нет. Некоторые сайты успешно используют смешанный рендеринг. Netflix генерирует на сервере относительно статические лендинги и в то же время предварительно загружает JavaScript для страниц с высоким уровнем интерактивности, давая возможность быстрее загрузиться страницам, которые больше используют клиентский рендеринг.
4–5 декабря, Онлайн, Беcплатно
Многие современные фреймворки, библиотеки и архитектуры позволяют рендерить одно и то же приложение как на клиенте, так и на сервере. Их возможности можно использовать и для серверного рендеринга, однако важно отметить, что архитектуры, в которых рендеринг происходит и на клиенте, и на сервере, являются отдельным классом решений со своими характеристиками производительности и недостатками. Пользователи React могут использовать для серверного рендеринга метод renderToString() или решения на его основе вроде Next.js. Пользователям Vue стоит обратить внимание на гайд Vue по серверному отображению или на Nuxt. Если ваш выбор — Angular, то посмотрите на Universal. Тем не менее в большинстве популярных решений присутствует какая-то форма гидратации, поэтому, прежде чем выбрать инструмент, разузнайте больше о используемом подходе.
Статический рендеринг
Статический рендеринг происходит на этапе сборки и предоставляет быструю первую отрисовку, первую содержательную отрисовку и время до интерактивности — при условии, что количество клиентского JavaScript ограничено. В отличие от серверного рендеринга здесь удаётся добиться стабильно быстрого времени до первого байта, так как HTML-код страницы не должен генерироваться на лету. Как правило, статический рендеринг подразумевает предварительное создание отдельного HTML-файла для каждого URL. Поскольку HTML-ответы созданы заранее, статический рендеринг можно развернуть на нескольких CDN, чтобы воспользоваться преимуществом кеширования.
Для статического рендеринга существуют самые разные решения. Инструменты вроде Gatsby разработаны так, чтобы создавать впечатление динамического рендеринга. Другие, вроде Jekyl и Metalsmith, принимают свою статическую природу и предлагают подход, основанный в большей степени на шаблонах.
Но у такого способа рендеринга есть один недостаток — необходимо заранее создать HTML-файлы для всех возможных URL. Это может быть очень сложно или даже невыполнимо, если вы не можете заранее сказать, какие URL возможны, или если у вас сайт с большим количеством уникальных страниц.
Пользователи React могут быть знакомы с Gatsby, статическим экспортом Next.js или Navi — все они делают использование компонентов удобнее. Однако важно понимать разницу между статическим рендерингом и пререндерингом: статически отрендеренные страницы не нуждаются в выполнении большого количества клиентского JS для интерактивности, в то время как пререндеринг улучшает первую (содержательную) отрисовку одностраничного приложения, которое должно быть загружено на клиент, чтобы страницы были действительно интерактивными.
Если вы не уверены, используется ли в решении статический рендеринг или пререндеринг, то попробуйте провести тест: отключите JavaScript и загрузите созданные страницы. У статически отрендеренных страниц функциональность по большей части останется на месте. У пререндеренных страниц может сохраниться базовая функциональность вроде ссылок, однако в целом страница будет не интерактивна.
Также можно устроить другую проверку: замедлите сеть с помощью инструментов разработчика и посмотрите, сколько JavaScript загружается, прежде чем страница становится интерактивной. Пререндерингу, как правило, требуется больше JavaScript для интерактивности, и обычно этот JavaScript более сложный, чем подход прогрессивного улучшения, используемый при статическом рендеринге.
Серверный рендеринг против статического
Серверное отображение не панацея — его динамическая природа может сопровождаться множественными вычислительными затратами. Многие решения с серверным отображением не используют технологию early flush и могут оттянуть время до первого байта или удвоить количество отправляемых данных (например, встроенное состояние, используемое JS на клиенте). В React метод renderToString() может быть медленным из-за синхронности и однопоточности. Для эффективной реализации серверного рендеринга может потребоваться найти решение для кеширования компонентов, разобраться с управлением потребления памяти, применить мемоизацию и не только. По сути вы заново обрабатываете/собираете одно приложение несколько раз — на сервере и на клиенте. Тот факт, что серверный рендеринг может показать что-то быстрее, вовсе не означает, что нужно проделать меньше вычислительной работы.
Серверный рендеринг создаёт HTML для каждого URL при необходимости, но такой подход может работать медленнее, чем простая отправка статического контента. Если вы готовы поработать дополнительно, то с помощью связки серверного рендеринга с HTML-кешированием вы сможете сильно уменьшить время рендеринга. Преимуществом серверного рендеринга является возможность извлекать больше актуальных данных и отвечать на более полный список запросов, чем это возможно при статическом рендеринге. Персонализированные страницы — яркий пример запроса, который не очень хорошо ладит со статическим рендерингом.
Клиентский рендеринг
Клиентский рендеринг подразумевает рендеринг страниц прямо в браузере с помощью JavaScript. Вся логика, получение данных, шаблонизация и маршрутизация обрабатываются на клиенте, а не на сервере.
Основной недостаток клиентского рендеринга заключается в том, что количество необходимого JavaScript обычно увеличивается вместе с ростом приложения. Ситуация ухудшается с подключением новых JavaScript-библиотек, полифиллов и прочего стороннего кода, который соревнуется между собой за вычислительные мощности и часто требует обработки, прежде чем содержимое страницы можно будет отобразить. Решениям с клиентским рендерингом, которые полагаются на большие JavaScript-бандлы, стоит рассмотреть сильное разделение кода и ленивую загрузку JavaScript — «загружайте только то, что вам нужно и только когда это нужно». Для решений с минимумом интерактивности или её отсутствием серверный рендеринг может предоставить более масштабируемое решение этих проблем.
Если вы создаёте одностраничное приложение, то, определив основные части пользовательского интерфейса, которые используются на большинстве страниц, вы сможете использовать кеширование оболочки приложения. В сочетании с Service Worker’ами это даст сильный прирост ощущаемой производительности при повторных посещениях.
Совмещение серверного и клиентского рендеринга с помощью регидратации
Универсальный рендеринг (или просто «SSR») пытается устранить недостатки серверного и клиентского рендеринга, используя оба подхода. Навигационные запросы вроде полной загрузки или перезагрузки страницы обрабатываются сервером, который рендерит приложение в HTML, затем JavaScript и данные, используемые для рендеринга, встраиваются в итоговый документ. При правильной реализации время первой содержательной отрисовки будет как при серверном рендеринге, а повторный рендеринг будет производиться на клиенте с помощью техники, называемой (ре)гидратацией. Это новое решение, тем не менее не лишённое определённых проблем с производительностью.
Основной недостаток универсального рендеринга с регидратацией заключается в том, что такой подход может очень негативно повлиять на время до интерактивности даже при улучшении первой отрисовки. Страницы часто выглядят обманчиво готовыми и интерактивными, но по факту не могут никак реагировать на действия пользователя до выполнения JS на стороне клиента и присоединения обработчиков событий. Это может занять несколько секунд или даже минут на мобильных устройствах.
Возможно, вы и сами сталкивались с таким — страница уже какое-то время выглядит загруженной, но нажатия на элементы не дают эффекта. Это сильно удручает: «Почему ничего не происходит? Почему я не могу скроллить?».
Проблема регидратации: одно приложение по цене двух
Из-за JavaScript проблемы регидратации могут быть похуже, чем отложенная интерактивность. Для того чтобы клиентский JavaScript мог, не прибегая к новому запросу всех данных, использованных сервером для рендеринга HTML, продолжить работу с того места, где прекратил работу сервер, текущие решения на основе универсального рендеринга обычно сериализуют данные для интерфейса в документ в виде тегов
Что такое рендеринг на стороне сервера и нужен ли он мне?
В новом году начнем общение с вами с затравочной статьи о серверном рендеринге (server-side rendering). В случае вашей заинтересованности возможна более свежая публикация о Nuxt.js и дальнейшая издательская работа в этом направлении
С появлением современных фреймворков и библиотек для JavaScript, которые предназначены, прежде всего, для создания интерактивных веб-страниц и одностраничных приложений, сильно изменился весь процесс показа страниц пользователю.
До пришествия приложений, полностью генерируемых на JS в браузере, HTML-разметка выдавалась клиенту в ответ на HTTP-вызов. Это могло происходить путем возврата статического HTML-файла с контентом, либо путем обработки отклика при помощи какого-либо серверного языка (PHP, Python или Java), причем, более динамическим образом.
Подобное решение позволяет создавать отзывчивые сайты, работающие гораздо быстрее стандартных сайтов, действующих по модели «запрос-отклик», поскольку при работе устраняется время, проводимое запросом «в пути».
Типичный отклик, отправляемый сервером на запрос к сайту, написанному на React, будет выглядеть примерно так:
В этот момент уже можно воспользоваться встроенным в браузер инспектором HTML, чтобы просмотреть весь отображенный HTML. Однако, заглянув в исходный код, мы не увидим ничего кроме HTML, приведенного выше.
Почему это проблема?
Хотя такое поведение и не доставит проблем большинству наших пользователей либо при разработке приложения, оно может стать нежелательным, если:
“Ладно, но в демографическом отношении моя целевая аудитория точно не относится ни к одной из этих групп, так стоит ли мне волноваться?”
Есть еще две вещи, которые следует учитывать при работе с приложением, рендеринг в котором выполняется на стороне клиента: поисковики и присутствие в социальных сетях.
Сегодня из всех поисковиков лишь Google обладает некоторыми возможностями отобразить и сайт и учесть его JS прежде, чем отобразить страницу. Кроме того, хотя Google и сможет отобразить индексную страницу вашего сайта, известно, что могут возникать проблемы с навигацией по сайтам, на которых реализован роутер.
Это означает, что вашему сайту будет очень непросто забраться в топ выдачи любого поисковика кроме Google.
Та же проблема просматривается и в социальных сетях, например, в Facebook — если ссылкой на ваш сайт поделятся, то ни его название, ни картинка-превью не отобразятся как следует.
Как решить эту проблему
Есть несколько способов ее решения.
A — Попробуйте оставить все ключевые страницы вашего сайта статическими
Когда создается сайт-платформа, куда пользователю придется входить под своим логином, а без входа в систему контент посетителю не предоставляется, можно попробовать оставить статическими (написанными на HTML) общедоступные страницы вашего сайта, в частности, индекс, «о нас», «контакты» и не использовать JS при их отображении.
Поскольку ваш контент ограничен требованиями по входу в систему, он не будет индексироваться поисковиками, и им нельзя будет поделиться в социальных сетях.
B — Генерируйте части вашего приложения в виде HTML-страниц в процессе сборки
В проект можно добавить такие библиотеки как react-snapshot; они используются для генерации HTML-копий страниц вашего приложения и для сохранения их в специально предназначенном каталоге. Затем этот каталог развертывается наряду с пакетом JS. Таким образом, HTML будет подаваться с сервера вместе с откликом, и ваш сайт увидят в том числе те пользователи, у которых отключен JavaScript, а также заметят поисковики и т.д.
Как правило, сконфигурировать react-snapshot не составляет труда: достаточно добавить библиотеку в ваш проект и изменить сборочный скрипт следующим образом:
C — Создать на JS приложение, использующее серверный рендеринг
Один из важнейших выигрышных моментов у современного поколения приложений на JS заключается в том, что их можно запускать как на клиенте (в браузере), так и на сервере. Так удается генерировать HTML для страниц, являющихся более динамичными, таких, чей контент в период сборки еще не известен. Подобные приложения часто называют «изоморфными» или «универсальными».
Два наиболее популярных решения, обеспечивающих серверный рендеринг для React:
Создайте собственную реализацию SSR
Важно: если вы собираетесь попробовать самостоятельно создать собственную реализацию SSR для приложений на React, то должны будете обеспечить работу node-бэкенда для вашего сервера. Вы не сможете развернуть это решение на статическом хосте, как в случае со страницами github.
Первым делом нам понадобится создать приложение, точно как в случае с любым другим приложением React.
Давайте создадим входную точку:
И компонент-приложение (App):
А также “оболочку”, чтобы загрузить наше приложение:
Express – это мощный веб-сервер для node, pug – движок-шаблонизатор, который можно использовать с express, а babel-node – это обертка для node, обеспечивает транспиляцию на лету.
Сначала скопируем наш файл index.html и сохраним его как index.pug :
Создадим наш сервер:
Разберем этот файл по порядку.
При помощи renderToString мы отображаем наше приложение. Код выглядит точно как у входной точки, но такое совпадение не является обязательным.
Теперь, когда у нас есть отображенный HTML, мы приказываем express отобразить в ответ файл index.pug и заменить переменную app тем HTML, что мы получили.
Наконец, мы обеспечиваем запуск сервера и настраиваем его так, чтобы он слушал порт 3000.
Теперь нам осталось всего лишь добавить нужный скрипт в package.json :
Если все выглядит именно так, это означает, что серверный рендеринг работает как положено, и можно приступать к расширению вашего приложения!
Зачем же нам по-прежнему нужен bundle.js?
В случае такого крайне простого приложения, которое рассмотрено здесь, включать bundle.js не обязательно – без этого файла наше приложение все равно останется работоспособным. Но в случае с реальным приложением включить этот файл все-таки потребуется.
Это позволит браузерам, умеющим обрабатывать JavaScript, взять работу на себя и далее взаимодействовать с вашей страницей уже на стороне клиента, а тем, что не умеют разбирать JS – перейти на страницу с нужным HTML, который возвратил сервер.
О чем необходимо помнить
Притом, что серверный рендеринг выглядит достаточно незамысловато, при разработке приложений нужно обращать внимание на некоторые темы, на первый взгляд не вполне очевидные: