Server side html rendering

Что такое рендеринг на стороне сервера и нужен ли он мне?

В новом году начнем общение с вами с затравочной статьи о серверном рендеринге (server-side rendering). В случае вашей заинтересованности возможна более свежая публикация о Nuxt.js и дальнейшая издательская работа в этом направлении

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

До пришествия приложений, полностью генерируемых на JS в браузере, HTML-разметка выдавалась клиенту в ответ на HTTP-вызов. Это могло происходить путем возврата статического HTML-файла с контентом, либо путем обработки отклика при помощи какого-либо серверного языка (PHP, Python или Java), причем, более динамическим образом.

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

Типичный отклик, отправляемый сервером на запрос к сайту, написанному на React, будет выглядеть примерно так:

Выбрав этот отклик, наш браузер также выберет «пакет» app.js , содержащий наше приложение, и через одну-две секунды полностью отобразит страницу.

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

Читайте также:  Оператор break в питоне

Почему это проблема?

Хотя такое поведение и не доставит проблем большинству наших пользователей либо при разработке приложения, оно может стать нежелательным, если:

  • Конечный пользователь работает с медленным интернет-соединением, что особенно актуально для мобильных устройств,
  • Конечный пользователь работает с маломощным устройством, например, со смартфоном старого поколения

“Ладно, но в демографическом отношении моя целевая аудитория точно не относится ни к одной из этих групп, так стоит ли мне волноваться?”

Есть еще две вещи, которые следует учитывать при работе с приложением, рендеринг в котором выполняется на стороне клиента: поисковики и присутствие в социальных сетях.

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

Это означает, что вашему сайту будет очень непросто забраться в топ выдачи любого поисковика кроме Google.

Та же проблема просматривается и в социальных сетях, например, в Facebook — если ссылкой на ваш сайт поделятся, то ни его название, ни картинка-превью не отобразятся как следует.

Как решить эту проблему

Есть несколько способов ее решения.

A — Попробуйте оставить все ключевые страницы вашего сайта статическими

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

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

B — Генерируйте части вашего приложения в виде HTML-страниц в процессе сборки

В проект можно добавить такие библиотеки как react-snapshot; они используются для генерации HTML-копий страниц вашего приложения и для сохранения их в специально предназначенном каталоге. Затем этот каталог развертывается наряду с пакетом JS. Таким образом, HTML будет подаваться с сервера вместе с откликом, и ваш сайт увидят в том числе те пользователи, у которых отключен JavaScript, а также заметят поисковики и т.д.

Как правило, сконфигурировать react-snapshot не составляет труда: достаточно добавить библиотеку в ваш проект и изменить сборочный скрипт следующим образом:

«build»: «webpack && react-snapshot —build-dir static»
Недостаток такого решения заключается в следующем: весь контент, который мы хотим сгенерировать, должен быть доступен во время сборки – мы не можем обратиться к каким-либо API, чтобы получить его, мы также не можем заранее сгенерировать контент, зависящий от данных, предоставляемых пользователем (например, от URL).

C — Создать на JS приложение, использующее серверный рендеринг

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

Два наиболее популярных решения, обеспечивающих серверный рендеринг для React:

Создайте собственную реализацию SSR

Важно: если вы собираетесь попробовать самостоятельно создать собственную реализацию SSR для приложений на React, то должны будете обеспечить работу node-бэкенда для вашего сервера. Вы не сможете развернуть это решение на статическом хосте, как в случае со страницами github.

Первым делом нам понадобится создать приложение, точно как в случае с любым другим приложением React.

Давайте создадим входную точку:

// index.js import React from 'react'; import < render >from 'react-dom'; import App from './App.js';render(, document.getElementById('root'));

И компонент-приложение (App):

// App.js import React from 'react';const App = () => < return ( 
Welcome to SSR powered React application!
); >

А также “оболочку”, чтобы загрузить наше приложение:

Как видите, приложение получилось довольно простым. В рамках этой статьи мы не будем пошагово разбирать все шаги, необходимые для генерации правильной сборки webpack+babel.
Если запустить приложение в его текущем состоянии, то на экране появится сообщение-приветствие. Просмотрев исходный код, вы увидите содержимое файла index.html , но приветственного сообщения там не будет. Для решения этой проблемы добавим серверный рендеринг. Для начала добавим 3 пакета:

yarn add express pug babel-node --save-dev

Express – это мощный веб-сервер для node, pug – движок-шаблонизатор, который можно использовать с express, а babel-node – это обертка для node, обеспечивает транспиляцию на лету.

Сначала скопируем наш файл index.html и сохраним его как index.pug :

Как видите, файл практически не изменился, не считая того, что теперь в HTML вставлено ! . Это переменная pug , которая впоследствии будет заменена реальным HTML.

// server.jsimport React from 'react'; import < renderToString >from 'react-dom/server'; import express from 'express'; import path from 'path';import App from './src/App';const app = express(); app.set('view engine', 'pug'); app.use('/', express.static(path.join(__dirname, 'dist')));app.get('*', (req, res) => < const html = renderToString( ); res.render(path.join(__dirname, 'src/index.pug'), < app: html >); >);app.listen(3000, () => console.log('listening on port 3000'));

Разберем этот файл по порядку.

import < renderToString >from 'react-dom/server';

Библиотека react-dom содержит отдельный именованный экспорт renderToString , работающий подобно известному нам render , но отображает не DOM, а HTML в виде строки.

const app = express(); app.set('view engine', 'pug'); app.use('/', express.static(path.join(__dirname, 'dist')));

Мы создаем новый инстанс сервера express и сообщаем ему, что собираемся использовать движок-шаблонизатор pug . В данном случае мы могли бы обойтись обычным считыванием файла и выполнением операции «поиск с заменой», но такой подход в самом деле неэффективен и может спровоцировать проблемы из-за множественного доступа к файловой системе, либо проблемы с кэшированием.

В последней строке мы приказываем express искать файл в каталоге dist , и, если запрос (напр. /bundle.js ) совпадает с файлом, присутствующем в этом каталоге, то выдать его в ответ.

Теперь мы приказываем express добавить обработчик на каждый несовпавший URL — в том числе, на наш несуществующий файл index.html (как вы помните, мы переименовали его в index.pug , и его нет в каталоге dist ).

const html = renderToString( );

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

res.render(path.join(__dirname, 'src/index.pug'), < app: html >);

Теперь, когда у нас есть отображенный HTML, мы приказываем express отобразить в ответ файл index.pug и заменить переменную app тем HTML, что мы получили.

app.listen(3000, () => console.log('listening on port 3000'));

Наконец, мы обеспечиваем запуск сервера и настраиваем его так, чтобы он слушал порт 3000.
Теперь нам осталось всего лишь добавить нужный скрипт в package.json :

Теперь, вызвав yarn run server , мы должны получить подтверждение, что сервер действительно работает. Переходим в браузере по адресу localhost:3000, где мы, опять же, должны увидеть наше приложение. Если мы просмотрим исходный код на данном этапе, то увидим:

     
div data-reactroot="">Welcome to SSR powered React application!

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

Зачем же нам по-прежнему нужен bundle.js?

В случае такого крайне простого приложения, которое рассмотрено здесь, включать bundle.js не обязательно – без этого файла наше приложение все равно останется работоспособным. Но в случае с реальным приложением включить этот файл все-таки потребуется.

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

О чем необходимо помнить

Притом, что серверный рендеринг выглядит достаточно незамысловато, при разработке приложений нужно обращать внимание на некоторые темы, на первый взгляд не вполне очевидные:

  • Любое состояние, сгенерированное на стороне сервера, не будет передаваться в состояние клиентского приложения. Это означает, что, если ваша серверная часть выберет некоторые данные и использует их для отображения HTML, то эти данные не попадут в this.state , которое увидит браузер
  • componentDidMount не вызывается на сервере — это означает, что не будут вызываться никакие операции по выборке данных, которые вы привыкли там размещать. В принципе, это хорошо, поскольку вы должны предоставлять нужные вам данные в виде пропсов. Помните, что отображение нужно отложить (вызвав res.render ) до тех пор, пока данные не будут выбраны. Из-за этого посетители могут заметить некоторые задержки в работе сайта
  • если вы собираетесь использовать роутер react (напр. @reach/router или react-router) то должны убедиться, что приложению передается правильный URL, когда оно отображается на сервере. Обязательно почитайте об этом в документации!

Источник

Оцените статью