Server Side Rendering для React App на Express.js
На написание этой статьи меня сподвигло отсутствие какого-либо более-менее полного мануала, как же сделать Server Side Rendering для React приложения.
Когда я столкнулся с этой проблемой, у меня было 2 варианта это сделать, либо Next.js фреймворк, либо используя Express.js.
На инвестигейт Next.js было потрачено около 100 часов, чтоб завести его для нашей готовой крупной OTT платформы, но проблем было настолько много, что мы от него отказались (по этому поводу напишу статью еще), остался выбор за малым, Express.js, про который я и хочу рассказать.
Полный код демо-примера, рассматриваемого в статье, тут.
Начнем с первоначальной задачи и то, что у нас было.
Мы имели на тот момент:
Из документации реакта, мы можем узнать, что для SSR можно использовать renderToString() и hydrate() методы, но что с ними делать дальше?
renderToString — используется для генерации HTML на сервере нашего приложения.
hydrate — используется для универсального рендера на клиенте и на сервере.
Загрузка данных
Для загрузки данных на стороне сервера используем библиотеку redux-connect, которая позволяет загружать необходимые данные, до вызова первого рендера, то, что нам и надо. Для этого используется hoc asyncConnect. На серверной части он загружает данные, а при роутинге он работает как componentDidMount.
Нам надо создать redux store на стороне сервера. Все как обычно, только создаем в файле server.js.
Также на стороне сервера с помощью метода loadOnServer из redux-connect дожидаемся предзагрузки данных.
С помощью renderToString мы получаем Html нашего приложения с данными.
Данные, которые мы засетили в стор, можно достать с помощью getState() и добавить через тег ‘, «); return res.send(data); >); >); store.close(); >); >);
В компонент ReduxAsyncConnect передаются 2 пропса:
первый — это наши роуты, второй хелперы (вспомогательные функции), которые мы хотим, чтоб были доступны по всему приложения, некий аналог context.
Для серверного роутинга надо использовать StaticRouter.
Для добавления seo meta тэгов используется бибилиотека helmet. В компоненте каждой страницы есть описание с тегами.
Для того, чтоб с сервера приходили сразу тэги тоже, используется
Роутинг надо было переписать на массив объектов, это выглядит так.
В зависимости от url, который приходит на сервер, react router отдавал нужную страницу с данными.
Как выглядит клиент?
Вот главный файл клиентской части. В него можно добавлять аналитику для поисковиков и код, который должен выполняться для каждой страницы на стороне клиента.
Для роутинга используется ConnectedRouter из connected-react-router/immutable.
Для серверного рендеринга мы не можем использовать react-router-dom и сообстветственно описать наш роутинг через Switch:
Вместо этого, как уже говорилось, у нас есть массив с описанными роутами, и для того, чтоб нам их добавить в приложение, нужно использовать react-router-config:
Загрузка каритнок, стилей и шрифтов на сервере
Для стилей использовался isomorphic-style-loader, так как обычный slyle-loader не работает в вебпаке с target: “node”;
Он добавляет все стили в DOM, тем самым с сервера приходит уже готовая, красивая страница. Можно сохранять в отдельный файл стили, тем самым они смогут кешироваться браузером.
Для отображения картинок и загрузки шрифтов на сервере был использован webpack loader base64-inline-loader.
Он был включен для изображений и всех типов шрифтов, которые мы использовали. В результате мы получали с сервера base64 код, который отображал страницу с шрифтами и изображениями без последующей загрузки.
Для клиентского билда использовался обычный url-loader и file-loader.
Да, это увеличило размер загружаемой страницы, но не сильно было заметно для пользователя по скорости загрузки.
Динамический импорт на сервере
В React.js для динамического импорта и отображения лоадера используются React.lazy и React.suspense, но они не работают для SSR.
Мы использовали react-loadable, который выполняет то же самое.
На клиенте этот код отображает лоадер, на сервере, для того, чтоб модули загрузились, нужно добавить следующий код:
Loadable.preloadAll() — возвращает промис, который говорит, что модули подгружены.
Все основные моменты решены.
Я сделал мини демо с использованием всего, что было описано в статье.
Серверный или клиентский рендеринг на вебе: что лучше использовать у себя в проекте и почему
Авторизуйтесь
Серверный или клиентский рендеринг на вебе: что лучше использовать у себя в проекте и почему
Разработчикам часто приходится принимать решения, которые повлияют на всю архитектуру приложения. Веб-разработчикам важно выбрать правильное место для реализации логики и рендеринга приложения. Это может быть непросто, так как сайт можно создать разными путями.
При выборе подхода для рендеринга нужно понимать разницу между возможными вариантами, чтобы не прогадать с производительностью. Сегодня мы разберёмся, в чём заключается эта разница.
Терминология
Отображение:
Производительность:
Серверный рендеринг
При серверном рендеринге в ответ на запрос на сервере генерируется весь HTML-код страницы. Это исключает необходимость дополнительных запросов данных со стороны клиента, так как сервер берёт всю работу на себя, прежде чем отправить ответ.
Такой подход позволяет добиться быстрой первой отрисовки и первой содержательной отрисовки. Выполнение логики страницы и рендеринг на сервере позволяют избежать отправки клиенту большого количества JavaScript, что приводит к меньшему времени до интерактивности. И это логично, ведь при серверном рендеринге пользователю отсылаются только текст и ссылки. Этот подход хорошо сработает на широком диапазоне устройств и сетевых условий, а также откроет возможности для интересных браузерных оптимизаций вроде потокового парсинга документа.
При использовании серверного рендеринга пользователям не нужно ждать завершения работы JavaScript, отнимающего ресурсы процессора, прежде чем они смогут начать работать с сайтом. Даже если нельзя избежать использования стороннего JavaScript, серверный рендеринг позволяет уменьшить количество вашего собственного JavaScript и даёт больше «бюджета» для всего остального. Однако у этого подхода есть один существенный недостаток: формирование страницы на сервере занимает определённое время, что может привести к большему времени до первого байта.
Ответ на вопрос «достаточно ли серверного рендеринга для моего приложения?» зависит от того, что вы создаёте. Сообщество давно дискутирует на тему правильного применения серверного рендеринга против клиентского, но важно помнить, что для одних страниц использовать серверный рендеринг можно, а для других — нет. Некоторые сайты успешно используют смешанный рендеринг. Netflix генерирует на сервере относительно статические лендинги и в то же время предварительно загружает JavaScript для страниц с высоким уровнем интерактивности, давая возможность быстрее загрузиться страницам, которые больше используют клиентский рендеринг.
3–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, продолжить работу с того места, где прекратил работу сервер, текущие решения на основе универсального рендеринга обычно сериализуют данные для интерфейса в документ в виде тегов
React Server-Side Rendering (SSR) — руководство новичка
В этом уроке мы поговорим о серверном рендеринге (SSR), его преимуществах и подводных камнях. Затем мы создадим мини React проект и express сервер (Node.js), чтобы продемонстрировать, как можно достичь SSR.
Почти каждый второй сайт на данный момент является одностраничным приложением (SPA). Я уверен вы знаете, что такое одностраничное приложение. Такие фреймворки как Angular, React, Vue, Svelte и т.д. находятся на подъеме из-за их способности быстро и эффективно разрабатывать SPA. Они идеально подходят не только для быстрого прототипирования, но и для разработки сложных веб-приложений (если всё сделано правильно).
До недавнего времени для большинства сайтов HTML генерировался на сервере и отправлялся вместе с ответом, чтобы браузер мог визуализировать его на экране. Всякий раз, когда пользователь нажимал на ссылку для доступа к новой странице, мы отправляли серверу новый запрос на генерацию нового HTML для этой страницы. Нет ничего плохого в этом подходе, кроме времени загрузки и опыта пользователя.
Пользователю приходилось ждать несколько секунд, пока сервер получит запрос, соберет данные, составит HTML и вернет ответ. Так как это было полностраничной загрузкой, браузер должен был ждать все ресурсы, такие как JavaScript, CSS, и другие файлы, чтобы загрузить их снова (если только они не кешируются должным образом). Это было огромным неудобством для пользователя.
В настоящее время большинство веб-приложений запрашивают у сервера только данные. Вся генерация HTML осуществляется на стороне клиента (браузер). Всякий раз, когда пользователь нажимает на ссылку, вместо отправки нового запроса на сервер для получения HTML этой страницы, мы создаем HTML на стороне клиента, монтируя новые компоненты (строительные блоки веб-приложений), и запрашиваем у сервера только те данные, которые нужны для наполнения этого HTML.
Таким образом, мы предотвращаем полную перезагрузку страницы и значительно улучшаем время загрузки страницы. Мы программно изменяем URL в браузере с помощью History API, что не приводит к обновлению браузера. Так как обновление страницы никогда не происходит, мы запрашиваем только начальный HTML, который включает в себя JavaScript, CSS и другие средства для всего приложения.
В приведенном выше HTML,
Давайте поговорим о преимуществах и подводных камнях SPA.
= Преимущества
SPA дают опыт взаимодействия похожий на нативные приложения. Так как весь HTML приложения генерируется на стороне клиента, загрузка страницы происходит очень быстро.
Так как все ресурсы приложения, такие как JavaScript и CSS файлы, загружаются только один раз и никогда не запрашиваются снова после загрузки приложения, мы сильно экономим на пропускной способности сервера.
После начальной загрузки приложения мы запрашиваем у сервера только данные размером в несколько килобайт (по запросу). Таким образом, SPA идеально подходят для использования в условиях плохой сети.
Всё приложение может быть кешировано на клиенте (устройстве) с помощью service worker. Таким образом, при следующем обращении пользователя к сайту, браузеру больше не нужно будет загружать HTML и ресурсы. Когда пользовательское устройство не подключено к интернету, вместо отображения сообщения браузера по умолчанию «Не подключено к интернету!», мы можем отобразить пользовательский экран, который даст пользователю оффлайн-доступ.
Пользователи могут сохранить SPA как приложение на устройстве. Если вы заинтересованы в разработке мобильного приложения но не хотите тратить деньги на разработку нативных приложений (Android или iOS), SPA открывают возможность создать приложение похожее на нативное, используя ту же самую кодовую базу веб-сайта.
= Подводные камни
Так как SPA должен обслуживать все JavaScript и CSS файлы приложения вместе (обычно), эти файлы громоздки (несколько мегабайт). Следовательно, начальная загрузка приложения требует значительно больше времени, что означает, что пользователь будет видеть пустой экран в течение довольно долгого времени. При плохой сети это может быть серьезной проблемой. Однако, мы можем исправить это с помощью SSR.
В SPA, так как JavaScript управляет генерацией HTML на стороне клиента, SPA делают тяжелую работу на стороне клиента, которую раньше выполнял сервер. Поэтому SPA нуждаются в устройствах с хорошими возможностями в части процессора и батареи.
Так как исходный HTML, предоставляемый сервером (для всех страниц) не содержит HTML, специфичный для конкретного применения, поисковый движок видит веб-сайт как пустой, не имеющий никакого содержимого. Таким образом, ваш сайт может не попасть в топ поисковых результатов, несмотря на огромный трафик и релевантное содержание.
Из приведенного выше сравнения видно, что преимущества SPA превосходят их подводные камни. Наши устройства с каждым днем становятся все более производительными, а условия работы сети становятся все лучше и лучше. Поэтому нам больше не нужно беспокоиться об этом.
Тем не менее, каждый сайт хочет занять первое место в результатах поиска. Когда дело доходит до SPA, этого не очень легко достичь. Как мы уже говорили, когда поисковая система (crawler), такая как Google, видит наш веб-сайт, она видит HTML с пустым элементом
Причина, по которой я выделил «при первой загрузке», заключается в том, что этот подход не похож на традиционный рендеринг HTML на стороне сервера всего приложения. Здесь мы генерируем HTML на сервере только один раз для запрашиваемой страницы, так что поисковые системы видят правильный HTML, в то время как приложение будет вести себя в браузере точно так же.
Этот метод имеет еще одно дополнительное преимущество. Так как сервер будет возвращать правильный HTML для страницы, пользователь больше не будет видеть пустой экран, пока все ресурсы приложения не будут загружены. Нам для этого нужно будет настроить несколько параметров, но зато это будет очень удобно для пользователя.
# Руководство по серверному рендерингу Vue.js
Для этого руководства требуются следующие версии Vue и библиотек:
Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь немного отличается (с новой опцией runInNewContext, установленной в false ). Ваше существующее приложение по-прежнему будет работать, но лучше внесите изменения с учётом новых рекомендаций.
# Что такое серверный рендеринг (SSR)?
Vue.js — это фреймворк для создания приложений, выполняемых на клиенте (client-side). По умолчанию компоненты Vue создают и манипулируют DOM в браузере. Однако, также возможно рендерить те же компоненты в HTML-строки на сервере, отправлять их в браузер, и наконец «гидрировать» статическую разметку в полностью интерактивное приложение на клиенте.
Приложение Vue.js отрендеренное на сервере также можно считать «изоморфным» или «универсальным», в том смысле, что большая часть кода приложения является общей для сервера и клиента.
# Нужен ли вам SSR?
По сравнению с традиционным SPA (Single-Page Application), преимуществами серверного рендеринга будут:
Лучшее SEO, поскольку поисковые роботы будут видеть полностью отрендеренную страницу.
Обратите внимание, что на данный момент Google и Bing могут без проблем индексировать синхронные приложения JavaScript. Ключевое слово здесь — синхронные. Если ваше приложение запускается с индикатором загрузки, а потом догружает контент через Ajax, то поисковый робот просто не будет дожидаться окончания загрузки. Это значит, что если у вас есть асинхронный контент на страницах где SEO важен, то может потребоваться серверный рендеринг.
Лучшие показатели времени до отображения контента (time-to-content), особенно при плохом интернете или на медленных устройствах. Для разметки, отрендеренной на сервере, не требуется дожидаться пока весь JavaScript будет загружен и выполнен, поэтому ваш пользователь увидит полностью отрендеренную страницу раньше. Как правило, это приводит к лучшему пользовательскому опыту и может быть критичным для приложений, где время до отображения контента напрямую связано с коэффициентом конверсии.
Следует учитывать и некоторые компромиссы при использовании серверного рендеринга:
Ограничения при разработке. Код только для браузера может быть использован лишь в определённых хуках жизненного цикла; некоторые внешние библиотеки могут нуждаться в особой обработке, чтобы иметь возможность запускаться в приложении с серверным рендерингом.
Более сложные требования по настройке и развёртыванию сборки. В отличие от полностью статичного SPA, который может быть развёрнут на любом статичном файловом сервере, приложение с серверным рендерингом требует окружения, где есть возможность запустить сервер Node.js.
Повышенная нагрузка на стороне сервера. Рендеринг полноценного приложения в Node.js очевидно более требователен к ресурсам процессора, чем простая раздача статичных файлов, поэтому если вы ожидаете большой трафик, будьте готовы к соответствующей нагрузке на сервер и используйте стратегии кэширования.
Прежде чем использовать серверный рендеринг для вашего приложения, задайте себе вопрос, действительно ли он вам нужен. Ответ зависит от того, насколько важно время до контента для вашего приложения. Например, если вы разрабатываете панель мониторинга для внутренних нужд, где дополнительные несколько сотен миллисекунд начальной загрузки не так важны, то серверный рендеринг будет излишеством. Однако, в тех случаях, когда время до контента критично, серверный рендеринг может позволит достичь наилучшей производительности начальной загрузки.
# SSR vs Пререндеринг
Если вы используете Webpack, то для добавления пререндеринга достаточно установить плагин prerender-spa-plugin
. Он был тщательно протестирован с приложениями Vue.
# Об этом руководстве
Это руководство ориентировано на SPA приложения с рендерингом на сервере, используя Node.js в качестве сервера. Использование серверного рендеринга Vue совместно с другими технологиями и настройками бэкэнда являются отдельной темой и кратко обсуждается в отдельном разделе.
Это руководство будет очень детальным и предполагает, что вы уже знакомы с самим Vue.js, имеете знания и опыт работы с Node.js и Webpack. Если вы предпочитаете более высокоуровневые решения, обеспечивающие работу из коробки — вам следует попробовать Nuxt.js
. Он построен на том же стеке Vue, но позволяет абстрагироваться от написания шаблонного кода, а также предоставляет некоторые дополнительные возможности, такие как генерация статичного сайта. Однако он может не подойти, если вам необходим полный контроль над структурой приложения. В любом случае, вам будет полезно прочитать это руководство, чтобы лучше понимать, как все составляющие работают вместе.
По мере прочтения руководства, будет полезным обращаться к официальному демо HackerNews
, в котором используется большинство техник, изложенных в этом руководстве.
Наконец, обратите внимание, что решения в этом руководстве не являются окончательными — мы решили, что они хорошо работают для нас, но это не означает, что они не могут быть улучшены. Они могут быть пересмотрены в будущем — поэтому не стесняйтесь вносить свой вклад, отправляя пулл-реквесты!



