URL: https://www.progressiverobot.com/react-server-side-rendering-ru/

Введение

_Рендеринг на стороне сервера_ (SSR) — это популярная методика рендеринга _одностраничного клиентского приложения_ (SPA) на сервере и последующей отправки на клиент полностью отрисованной страницы. Это позволяет использовать динамические компоненты в качестве статической разметки HTML.

Такой подход может быть полезным для поисковой оптимизации (SEO), когда при индексации код JavaScript не обрабатывается надлежащим образом. Это также может быть полезно в ситуациях, когда загрузка большого блока JavaScript затруднена из-за медленной скорости сети.

В этом учебном модуле мы инициализируем приложение React с помощью Create React App, а затем изменим проект, чтобы он активировал рендеринг на стороне сервера.

После прохождения учебного модуля вы получите работающий проект с клиентским приложением React и серверным приложением Express.

Примечание. Также Next.js позволяет использовать современный подход к созданию статических приложений React и приложений, рендеринг которых выполняется на сервере.

Предварительные требования

react illustration for: Предварительные требования

Для данного обучающего руководства вам потребуется следующее:

Этот учебный модуль был проверен с версиями Node v14.4.0 и npm v6.14.5.

Шаг 1 — Создание приложения React и изменение компонента приложения

Вначале мы используем npx для запуска нового приложения React с помощью последней версии Create React App.

Назовем наше приложение <^>my-ssr-app<^>:

				
					
npx create-react-app@3.4.1 &lt;^&gt;my-ssr-app&lt;^&gt;

				
			

Перейдем в новый каталог с помощью команды cd:

				
					
cd &lt;^&gt;my-ssr-app&lt;^&gt;

				
			

В заключение мы запустим наше новое приложение на стороне клиента для проверки установки:

				
					
npm start

				
			

Вы должны увидеть пример приложения React в окне браузера.

Теперь создадим компонент <Home>:

				
					
nano src/Home.js

				
			

Затем добавим следующий код в файл Home.js:

				
					
[label src/Home.js]

import React from 'react';



export default props =&gt; {

 return &lt;h1&gt;Hello {props.name}!&lt;/h1&gt;;

};

				
			

При этом будет создан заголовок <h1> с сообщением "Hello", адресованным имени.

Далее мы выполним рендеринг <Home> в компоненте <App>. Откройте файл App.js:

				
					
nano src/App.js

				
			

Затем заменим существующие строки кода новыми строками кода:

				
					
[label src/App.js]

import React from 'react';

&lt;^&gt;import Home from './Home';&lt;^&gt;



&lt;^&gt;export default () =&gt; {&lt;^&gt;

 &lt;^&gt;return &lt;Home name="Sammy" /&gt;;&lt;^&gt;

&lt;^&gt;};&lt;^&gt;

				
			

Они передают name в компонент <Home> так, что ожидаемое сообщение будет выглядеть так: "Hello <^>Sammy<^>!".

В файле index.js нашего приложения мы будем использовать метод ReactDOM hydrate вместо render, чтобы указать блоку рендеринга DOM, чтобы мы восстанавливаем приложение после рендеринга на стороне сервера.

Откроем файл index.js:

				
					
nano index.js

				
			

Замените содержимое файла index.js следующим кодом:

				
					
[label index.js]

import React from 'react';

import ReactDOM from 'react-dom';

import App from './App';



ReactDOM.hydrate(&lt;App /&gt;, document.getElementById('root'));

				
			

Мы завершили настройку на стороне клиента и теперь можем перейти к настройке на стороне сервера.

Шаг 2 — Создание сервера Express и рендеринг компонента приложения

Теперь наше приложение готово, и мы настроим сервер, который будет отправлять готовую версию после рендеринга. Для сервера мы будем использовать Express. Добавим его в проект, введя следующую команду в окне терминала:

				
					
npm install express&lt;^&gt;@4.17.1&lt;^&gt;

				
			

Также можно использовать yarn:

				
					
yarn add express&lt;^&gt;@4.17.1&lt;^&gt;

				
			

Создайте каталог server рядом с каталогом src нашего приложения:

				
					
mkdir server

				
			

Затем создайте новый файл index.js, содержащий код сервера Express:

				
					
nano server/index.js

				
			

Добавим необходимые элементы импорта и определим некоторые константы:

				
					
[label server/index.js]

import path from 'path';

import fs from 'fs';



import React from 'react';

import express from 'express';

import ReactDOMServer from 'react-dom/server';



import App from '../src/App';



const PORT = process.env.PORT || 3006;

const app = express();

				
			

Затем добавим код сервера с обработкой ошибок:

				
					
[label server/index.js]

// ...



app.get('/', (req, res) =&gt; {

 const app = ReactDOMServer.renderToString(&lt;App /&gt;);



 const indexFile = path.resolve('./build/index.html');

 fs.readFile(indexFile, 'utf8', (err, data) =&gt; {

 if (err) {

 console.error('Something went wrong:', err);

 return res.status(500).send('Oops, better luck next time!');

 }



 return res.send(

 data.replace('&lt;div id="root"&gt;&lt;/div&gt;', `&lt;div id="root"&gt;${app}&lt;/div&gt;`)

 );

 });

});



app.use(express.static('./build'));



app.listen(PORT, () =&gt; {

 console.log(`Server is listening on port ${PORT}`);

});

				
			

Как видите, мы можем импортировать наш компонент <App> из клиентского приложения непосредственно с сервера.

Здесь происходит три важные вещи:

  • Мы предписываем Express вывести содержимое каталога build в виде статичных файлов.
  • Мы будем использовать метод ReactDOMServer, renderToString, для рендеринга нашего приложения в статичную строку HTML.
  • Затем мы считываем статичный файл index.html из готового клиентского приложения, вставляем статичное содержимое нашего приложения в <div> с id "root", а затем отправляем результат в качестве ответа на запрос.

Шаг 3 — Настройка webpack, Babel и скриптов npm

Чтобы наш серверный код работал, нам нужно объединить его в комплект и провести транспиляцию, используя webpack и Babel. Для этого добавим в проект зависимости dev, введя следующую команду в окне терминала:

				
					
npm install webpack&lt;^&gt;@4.42.0&lt;^&gt; webpack-cli&lt;^&gt;@3.3.12&lt;^&gt; webpack-node-externals&lt;^&gt;@1.7.2&lt;^&gt; @babel/core&lt;^&gt;@7.10.4&lt;^&gt; babel-loader&lt;^&gt;@8.1.0&lt;^&gt; @babel/preset-env&lt;^&gt;@7.10.4&lt;^&gt; @babel/preset-react&lt;^&gt;@7.10.4&lt;^&gt; --save-dev

				
			

Также можно использовать yarn:

				
					
yarn add webpack&lt;^&gt;@4.42.0&lt;^&gt; webpack-cli&lt;^&gt;@3.3.12&lt;^&gt; webpack-node-externals&lt;^&gt;@1.7.2&lt;^&gt; @babel/core&lt;^&gt;@7.10.4&lt;^&gt; babel-loader&lt;^&gt;@8.1.0&lt;^&gt; @babel/preset-env&lt;^&gt;@7.10.4&lt;^&gt; @babel/preset-react&lt;^&gt;@7.10.4&lt;^&gt; --dev

				
			

Примечание. В более ранней версии этого учебного модуля мы устанавливали babel-core, babel-preset-env и babel-preset-react-app. Эти пакеты с тех пор были архивированы, и вместо них используются версии с одним репозиторием.

Далее мы создадим файл конфигурации Babel:

				
					
nano .babelrc.json

				
			

После этого добавьте готовые настройки env и react-app:

				
					
[label .babelrc.json]

{

 "presets": [

 "@babel/preset-env",

 "@babel/preset-react"

 ]

}

				
			

Примечание. В более ранней версии этого учебного модуля мы использовали файл .babelrc (без расширения .json). Это был файл конфигурации Babel 6, однако для Babel 7 он больше не используется.

Теперь мы создадим конфигурацию webpack для сервера, который использует Babel Loader для транспиляции кода. Начните с создания файла:

				
					
nano webpack.server.js

				
			

Затем добавьте следующие конфигурации в файл webpack.server.js:

				
					
[label webpack.server.js]

const path = require('path');

const nodeExternals = require('webpack-node-externals');



module.exports = {

 entry: './server/index.js',



 target: 'node',



 externals: [nodeExternals()],



 output: {

 path: path.resolve('server-build'),

 filename: 'index.js'

 },



 module: {

 rules: [

 {

 test: /\.js$/,

 use: 'babel-loader'

 }

 ]

 }

};

				
			

С этой конфигурацией наш транспилированный серверный комплект будет выводиться в папку server-build в файле с именем called index.js.

Обратите внимание на использование target: 'node' и externals: [nodeExternals()] из webpack-node-externals. При этом опускаются файлы из node_modules в комплекте, сервер сможет получить доступ к этим файлам напрямую.

Это завершает установку зависимости и конфигурации webpack и Babel.

Теперь мы снова вернемся к файлу package.json и добавим вспомогательные скрипты npm:

				
					
nano package.json

				
			

Мы добавим скрипты dev:build-server, dev:start и dev в файл package.json, чтобы легко выполнять сборку и подачу нашего приложения SSR:

				
					
[label package.json]

"scripts": {

 &lt;^&gt;"dev:build-server": "NODE_ENV=development webpack --config webpack.server.js --mode=development -w",&lt;^&gt;

 &lt;^&gt;"dev:start": "nodemon ./server-build/index.js",&lt;^&gt;

 &lt;^&gt;"dev": "npm-run-all --parallel build dev:*",&lt;^&gt;

 ...

},

				
			

Мы используем nodemon для перезапуска сервера при внесении изменений. Также мы используем npm-run-all для параллельного выполнения нескольких команд.

Давайте установим эти пакеты, введя следующие команды в окне терминала:

				
					
npm install nodemon&lt;^&gt;@2.0.4&lt;^&gt; npm-run-all&lt;^&gt;@4.1.5&lt;^&gt; --save-dev

				
			

Также можно использовать yarn:

				
					
yarn add nodemon&lt;^&gt;@2.0.4&lt;^&gt; npm-run-all&lt;^&gt;@4.1.5&lt;^&gt; --dev

				
			

Так вы можете запустить следующий код для сборки приложения на стороне клиента, объединения в пакет и транспиляции кода сервера и запуска сервера на порту :3006:

				
					
npm run dev

				
			

Также можно использовать yarn:

				
					
yarn run dev

				
			

Наша конфигурация сервера webpack будет следить за изменениями, и в случае изменений наш сервер перезапустится. Однако для клиентского приложения нам нужно выполнять сборку каждый раз при внесении изменений. Для этого есть открытая проблема.

Откройте в браузере адрес http://localhost:3006/ и вы увидите приложение после рендеринга на стороне сервера.

Ранее исходный код показал следующее:

				
					
[secondary_label Output]

&lt;div id="root"&gt;&lt;/div&gt;

				
			

С внесенными изменениями исходный код показывает:

				
					
[secondary_label Output]

&lt;div id="root"&gt;&lt;h1 data-reactroot=""&gt;Hello &lt;!-- --&gt;Sammy&lt;!-- --&gt;!&lt;/h1&gt;&lt;/div&gt;

				
			

При рендеринге на стороне сервера компонент <App> был успешно конвертирован в формат HTML.

Заключение

В этом учебном модуле мы инициализировали приложение React и активировали рендеринг на стороне сервера.

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

Преимущество использования SSR заключается в наличии приложения, содержимое которого может просмотреть даже сборщик, не выполняющий код JavaScript. Это поможет для поисковой оптимизации (SEO) и отправки метаданных на каналы социальных сетей.

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

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