- Что такое рендеринг на стороне сервера и нужен ли он мне?
- Почему это проблема?
- Как решить эту проблему
- A — Попробуйте оставить все ключевые страницы вашего сайта статическими
- B — Генерируйте части вашего приложения в виде HTML-страниц в процессе сборки
- C — Создать на JS приложение, использующее серверный рендеринг
- Создайте собственную реализацию SSR
- Зачем же нам по-прежнему нужен bundle.js?
- О чем необходимо помнить
Что такое рендеринг на стороне сервера и нужен ли он мне?
В новом году начнем общение с вами с затравочной статьи о серверном рендеринге (server-side rendering). В случае вашей заинтересованности возможна более свежая публикация о Nuxt.js и дальнейшая издательская работа в этом направлении
С появлением современных фреймворков и библиотек для JavaScript, которые предназначены, прежде всего, для создания интерактивных веб-страниц и одностраничных приложений, сильно изменился весь процесс показа страниц пользователю.
До пришествия приложений, полностью генерируемых на JS в браузере, HTML-разметка выдавалась клиенту в ответ на HTTP-вызов. Это могло происходить путем возврата статического HTML-файла с контентом, либо путем обработки отклика при помощи какого-либо серверного языка (PHP, Python или Java), причем, более динамическим образом.
Подобное решение позволяет создавать отзывчивые сайты, работающие гораздо быстрее стандартных сайтов, действующих по модели «запрос-отклик», поскольку при работе устраняется время, проводимое запросом «в пути».
Типичный отклик, отправляемый сервером на запрос к сайту, написанному на React, будет выглядеть примерно так:
Выбрав этот отклик, наш браузер также выберет «пакет» app.js , содержащий наше приложение, и через одну-две секунды полностью отобразит страницу.
В этот момент уже можно воспользоваться встроенным в браузер инспектором HTML, чтобы просмотреть весь отображенный HTML. Однако, заглянув в исходный код, мы не увидим ничего кроме HTML, приведенного выше.
Почему это проблема?
Хотя такое поведение и не доставит проблем большинству наших пользователей либо при разработке приложения, оно может стать нежелательным, если:
- Конечный пользователь работает с медленным интернет-соединением, что особенно актуально для мобильных устройств,
- Конечный пользователь работает с маломощным устройством, например, со смартфоном старого поколения
“Ладно, но в демографическом отношении моя целевая аудитория точно не относится ни к одной из этих групп, так стоит ли мне волноваться?”
Есть еще две вещи, которые следует учитывать при работе с приложением, рендеринг в котором выполняется на стороне клиента: поисковики и присутствие в социальных сетях.
Сегодня из всех поисковиков лишь 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!