Русский
Русский
English
Статистика
Реклама

Server-side rendering

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

04.01.2021 10:13:21 | Автор: admin
Привет, Хабр!

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


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

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

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

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

<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8">    <link rel="shortcut icon" href="http://personeltest.ru/aways/habr.com/favicon.ico">    <title>React App</title>  </head>  <body>    <div id="root"></div>    <script src="http://personeltest.ru/aways/habr.com/app.js"></script>  </body></html>


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

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

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



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

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


Если с демографической точки зрения ваша целевая аудитория относится к одной из этих групп, то работать с сайтом будет неудобно в частности, пользователям придется довольно долго ждать, уставившись в надпись Loading (или того хуже в пустой экран).

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

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

Сегодня из всех поисковиков лишь 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.jsimport React from 'react';import { render } from 'react-dom';import App from './App.js';render(<App />, document.getElementById('root'));


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

// App.jsimport React from 'react';const App = () => {  return (    <div>      Welcome to SSR powered React application!    </div>  );}


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

// index.html<!doctype html><html>  <head>    <meta charset="utf-8" />  </head>  <body>    <div id="root"></div>    <script src="http://personeltest.ru/aways/habr.com/bundle.js"></script>  </body></html>


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

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

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

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

// index.pug
<!doctype html>
<html>
<head>
<meta charset=utf-8 />
</head>
<body>
<div id=root>!{app}</div>
<script src=bundle.js></script>
</body>
</html>

Как видите, файл практически не изменился, не считая того, что теперь в HTML вставлено !{app}. Это переменная 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(    <App />  );  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) совпадает с файлом, присутствующем в этом каталоге, то выдать его в ответ.

app.get('*', (req, res) => {});


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

const html = renderToString(  <App />);


При помощи 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:

"scripts": {  "server": "babel-node server.js"}


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

<!doctype html><html>  <head>    <meta charset="utf-8" />  </head>  <body>    <div id="root">div data-reactroot="">Welcome to SSR powered React application!</div></div>    <script src="bundle.js"></script>  </body></html>


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

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



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

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

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



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

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

Server-Side Rendering с нуля до профи

18.01.2021 14:21:57 | Автор: admin

  • В данной статье мы разберем влияние SSR на SEO оптимизацию приложения.
  • Пройдем с вами путь по переносу обычного React приложения на SSR.
  • Разберем обработку асинхронных операций в SSR приложениях.
  • Посмотрим, как делать SSR в приложениях с Redux Saga.
  • Настроим Webpack 5 для работы с SSR приложением.
  • А также рассмотрим тонкости работы SSR: Генерация HTML Meta Tags, Dynamic Imports, работа с LocalStorage, debugging и прочее.




Пару лет назад, работая над своим продуктом Cleverbrush мой друг и я столкнулись с проблемой SEO оптимизации. Созданный нами сайт, который по идее должен был продавать наш продукт, а это был обычный Single Page React Application, не выводился в Google выборке даже по ключевым словам! В ходе детального разбора данной проблемы родилась библиотека iSSR, а также наш сайт начал появляться на первой странице Google. Итак, давайте по порядку!

Проблема



Главной проблемой Single Page приложений является то, что сервер отдает клиенту пустую HTML страницу. Её формирование происходит только после того как весь JS будет скачан (это весь ваш код, библиотеки, фреймверк). Это в большинстве случаев более 2-х мегабайт размера + задержки на обработку кода.

Даже если Google-бот умеет выполнять JS, он получает контент только спустя некоторое время, критичное для ранжирования сайта. Google-бот попросту видит пустую страницу несколько секунд! Это плохо!

Google начинает выдавать красные карты если ваш сайт рендерится более 3-х секунд. First Contentful Paint, Time to Interactive это метрики которые будут занижены при Single Page Application. Подробнее читайте здесь.

Также есть менее продвинутые поисковые системы, которые попросту не умеют работать с JS. В них Single Page Application не будут индексироваться.

На ранжирование сайта еще влияет множество факторов, часть из них мы разберем далее в этой статье.

Рендеринг



Существует несколько путей как решить проблему пустой странички при загрузке, рассмотрим несколько из них:

Static Site Generation (SSG). Сделать пререндер сайта перед тем как его загрузить на сервер. Очень простое и эффективное решение. Отлично подходит для простых веб страничек, без взаимодействия с backend API.

Server-Side Rendering (SSR). Рендерить контент в рантайме на сервере. При таком подходе мы сможем делать запросы backend API и отдавать HTML вместе с необходимым содержимым.

Server-Side Rendering (SSR)



Рассмотрим подробнее, как работает SSR:

  • У нас должен быть сервер, который выполняет наше приложение точно так же, как делал бы это пользователь в браузере. Делая запросы на необходимые ресурсы, отображая весь необходимый HTML, наполняя состояние.
  • Сервер отдает клиенту наполненный HTML, наполненное состояние, а также отдает все необходимые JS, CSS и прочие ресурсы.
  • Клиент, получая HTML и ресурсы, синхронизирует состояние и работает с приложением как с обычным Single Page Application. При этом важным моментом является то, что состояние должно синхронизироваться.


Схематично SSR приложение выглядит вот так:

SSR

Из вышеописанной работы SSR приложения мы можем выделить проблемы:

  • Приложение делится на сервер и клиент. То есть у нас по сути получается 2 приложения. Данное разделение должно быть минимально иначе поддержка такого приложения будет сложной.
  • Сервер должен уметь обрабатывать запросы к API с данными. Данные операции асинхронные, являются Side Effects. По умолчанию renderToString метод React работающий с сервером синхронный и не может работать с асинхронными операциями.
  • На клиенте приложение должно синхронизировать состояние и продолжать работать как обычное SPA приложение.


iSSR



Это маленькая библиотека которая способна решить проблемы асинхронной обработки запросов к данным а также синхронизации состояния с сервера на клиент. Это не очередной убийца Next.JS, нет! Next.JS прекрасный фреймверк имеющий множество возможностей, но чтобы его использовать вам придется почти полностью переписать ваше приложение и следовать правилам Next.JS.

Посмотрим на примере, как просто перенести обычное SPA приложение на SSR.

К примеру, у нас есть простейшее приложение с асинхронной логикой.

Код приложения
import React, { useState, useEffect } from 'react';import { render } from 'react-dom';const getTodos = () => {  return fetch('https://jsonplaceholder.typicode.com/todos')    .then(data => data.json())};const TodoList = () => {  const [todos, setTodos] = useState([]);  useEffect(() => {    getTodos()      .then(todos => setTodos(todos))  }, []);  return (    <div>      <h1>Hi</h1>      <ul>        {todos.map(todo => (          <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.title}</li>        ))}      </ul>    </div>  )}render(  <TodoList />,  document.getElementById('root'));



Данный код рендерит список выполненных задач, используя сервис jsonplaceholder для эмуляции взаимодействия с API.

Сделаем данное приложение SSR!



Шаг 1. Установка зависимостей



Для установки iSSR нужно выполнить:

npm install @issr/core --savenpm install @issr/babel-plugin --save-dev


Для настройки базовой билд системы установим:

npm install @babel/core @babel/preset-react babel-loader webpack webpack-cli nodemon-webpack-plugin --save-dev


Один из неочевидных моментов разработки SSR приложений является то, что некоторые API и библиотеки могут работать на клиенте но не работать на сервере. Одним из таких API является fetch. Данный метод отсутствует в nodejs где будет выполняться серверная логика нашего приложения. Для того, чтобы у нас приложение работало одинаково установим пакет:

npm install node-fetch --save


Для сервера будем использовать express, но это не важно, можно использовать любой другой фреймверк:

npm install express --save


Добавим модуль для сериализации состояния приложения на сервере:
npm install serialize-javascript --save


Шаг 2. Настройка webpack.config.js



webpack.config.js
const path = require('path');const NodemonPlugin = require('nodemon-webpack-plugin');const commonConfig = {  module: {    rules: [      {        test: /\.jsx$/,        exclude: /node_modules/,        use: [          {            loader: 'babel-loader',            options: {              presets: [                '@babel/preset-react'              ],              plugins: [                '@issr/babel-plugin'              ]            }          }        ]      }    ]  },  resolve: {    extensions: [      '.js',      '.jsx'    ]  }}module.exports = [  {    ...commonConfig,    target: 'node',    entry: './src/server.jsx',    output: {      path: path.resolve(__dirname, './dist'),      filename: 'index.js',    },    plugins: [      new NodemonPlugin({        watch: path.resolve(__dirname, './dist'),      })    ]  },  {    ...commonConfig,    entry: './src/client.jsx',    output: {      path: path.resolve(__dirname, './public'),      filename: 'index.js',    }  }];



  • Для компиляции SSR приложения конфигурационный файл webpack должен состоять из двух конфигураций (MultiCompilation). Одна для сборки сервера, вторая для сборки клиента. Мы передаем в module.exports массив.
  • Для конфигурации сервера нам нужно задать target: 'node'. Для клиента задавать target не обязательно. По умолчанию конфигурация webpack имеет target: web. target: node позволяет webpack обрабатывать сервер код, модули по умолчанию, такие как path, child_process и прочее.
  • const commonConfig общая часть настроек. Так как код сервера и клиента имеет общую структуру приложения, они должны обрабатывать JS одинаково.


В babel-loader необходимо добавить плагин:
@issr/babel-plugin
Это вспомогательный модуль @issr/babel-plugin позволяющий отследить асинхронные операции в приложении. Замечательно работает с babel/typescript-preset, и прочими babel плагинами.

Шаг 3. Модификация кода.



Вынесем общую логику нашего приложения в отдельный файл App.jsx. Это нужно для того, чтобы в файлах client.jsx и server.jsx осталась только логика рендеринга, ничего больше. Таким образом весь код приложения у нас будет общий.

App.jsx
import React from 'react';import fetch from 'node-fetch';import { useSsrState, useSsrEffect } from '@issr/core';const getTodos = () => {  return fetch('https://jsonplaceholder.typicode.com/todos')    .then(data => data.json())};export const App = () => {  const [todos, setTodos] = useSsrState([]);  useSsrEffect(async () => {    const todos = await getTodos()    setTodos(todos);  });  return (    <div>      <h1>Hi</h1>      <ul>        {todos.map(todo => (          <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.title}</li>        ))}      </ul>    </div>  );};



client.jsx
import React from 'react';import { hydrate } from 'react-dom';import { App } from './App';hydrate(  <App />,  document.getElementById('root'));



Мы поменяли стандартный render метод React на hydrate, который работает для SSR приложений.

server.jsx
import React from 'react';import express from 'express';import { renderToString } from 'react-dom/server';import { App } from './App';const app = express();app.use(express.static('public'));app.get('/*', async (req, res) => {const html = renderToString(<App />);  res.send(`  <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body>    <div id="root">${html}</div></body></html>`);});app.listen(4000, () => {  console.log('Example app listening on port 4000!');});



В коде сервера обратите внимание, что мы должны расшаривать папку с собранным SPA приложением webpack:
app.use(express.static('public'));
Таким образом, полученный с сервера HTML будет работать далее как обычный SPA

Шаг 4. Обработка асинхронности.



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

Для обработки асинхронных операций их нужно обернуть в хук useSsrEffect из пакета @issr/core:

App.jsx
import React from 'react';import fetch from 'node-fetch';import { useSsrEffect } from '@issr/core';const getTodos = () => {  return fetch('https://jsonplaceholder.typicode.com/todos')    .then(data => data.json())};export const App = () => {  const [todos, setTodos] = useState([]);  useSsrEffect(async () => {    const todos = await getTodos()    setTodos(todos);  });  return (    <div>      <h1>Hi</h1>      <ul>        {todos.map(todo => (          <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.title}</li>        ))}      </ul>    </div>  );};



В server.jsx заменим стандартный renderToString на serverRender из пакета @issr/core:

server.jsx
import React from 'react';import express from 'express';import { serverRender } from '@issr/core';import serialize from 'serialize-javascript';import { App } from './App';const app = express();app.use(express.static('public'));app.get('/*', async (req, res) => {  const { html } = await serverRender(() => <App />);  res.send(`  <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body>    <div id="root">${html}</div>    <script src="http://personeltest.ru/aways/habr.com/index.js"></script></body></html>`);});app.listen(4000, () => {  console.log('Example app listening on port 4000!');});



Если запустить приложение сейчас, то ничего не произойдет! Мы не увидим результата выполнения асинхронной функции getTodos. Почему так происходит? Мы забыли синхронизировать состояние. Давайте исправим это.

В App.jsx заменим стандартный setState на useSsrState из пакета @issr/core:

App.jsx
import React from 'react';import fetch from 'node-fetch';import { useSsrState, useSsrEffect } from '@issr/core';const getTodos = () => {  return fetch('https://jsonplaceholder.typicode.com/todos')    .then(data => data.json())};export const App = () => {  const [todos, setTodos] = useSsrState([]);  useSsrEffect(async () => {    const todos = await getTodos()    setTodos(todos);  });  return (    <div>      <h1>Hi</h1>      <ul>        {todos.map(todo => (          <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.title}</li>        ))}      </ul>    </div>  );};



Внесем изменения в client.jsx для синхронизации состояния переданного с сервера на клиент:

client.jsx
import React from 'react';import { hydrate } from 'react-dom';import createSsr from '@issr/core';import { App } from './App';const SSR = createSsr(window.SSR_DATA);hydrate(  <SSR>    <App />  </SSR>,  document.getElementById('root'));



window.SSR_DATA это объект, переданный с сервера с кешированнным состоянием, для синхронизации на клиенте.

Сделаем передачу состояние на сервере:

server.jsx
import React from 'react';import express from 'express';import { serverRender } from '@issr/core';import serialize from 'serialize-javascript';import { App } from './App';const app = express();app.use(express.static('public'));app.get('/*', async (req, res) => {  const { html, state } = await serverRender(() => <App />);  res.send(`  <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title>    <script>      window.SSR_DATA = ${serialize(state, { isJSON: true })}    </script></head><body>    <div id="root">${html}</div>    <script src="http://personeltest.ru/aways/habr.com/index.js"></script></body></html>`);});app.listen(4000, () => {  console.log('Example app listening on port 4000!');});



Обратите внимание, что функция serverRender передает не только HTML, но и состояние, которое прошло через useSsrState, мы его передаем на клиент, в качестве глобальной переменной SSR_DATA. На клиенте, данное состояние будет автоматически синхронизировано.

Шаг 5. Билд скрипты.


Осталось добавить скрипты запуска в package.json:

"scripts": { "start": "webpack -w --mode development", "build": "webpack"},


Redux и прочие State Management библиотеки



iSSR отлично поддерживает разные state management библиотеки. В ходе работы над iSSR я заметил, что React State Management библиотеки делятся на 2 типа:
  1. Реализует работу с Side Effects на слое React. Например Redux Thunk превращает вызов Redux dispatch в асинхронный метод, а значит мы можем имплементить SSR как в примере выше для setState. Пример с redux-thunk доступен по ссылке
  2. Реализуют работу с Side Effects на отдельном от React слое. Например Redux Saga выносит работу с асинхронными операциями в Саги.


Рассмотрим пример реализации SSR для приложения с Redux Saga.

Мы не будем рассматривать этот пример так детально как предыдущий. Ознакомится с полным кодом можно по ссылке.

Redux Saga


*Для лучшего понимания происходящего, читайте предыдущую главу

Сервер запускает наше приложение через serverRender, код выполняется последовательно, выполняя все операции useSsrEffect.

Концептуально Redux работая с сагами не выполняет никаких асинхронных операций. Наша задача отправить action для старта асинхронной операции в слое Саг, отдельных от нашего react-flow. В примере по ссылке выше, в контейнере Redux мы выполняем

useSsrEffect(() => { dispatch(fetchImage());});


Это не асинхронная операция! Но iSSR понимает, что что то произошло в системе. iSSR будет идти по остальным React компонентам выполняя все useSsrEffect если таковые будут и по завершению iSSR вызовет каллбек:

const { html } = await serverRender(() => ( <Provider store={store}>   <App /> </Provider>), async () => { store.dispatch(END); await rootSaga.toPromise();});


Таким образом мы можем обрабатывать асинхронные операции не только на уровне с React но и на других уровнях, в данном случае мы в начале поставили на выполнение нужные нам саги, после чего в callback serverRender запустили и дождались их окончания.

Я подготовил много примеров использования iSSR, вы можете их найти по ссылке.

SSR трюки



На пути в разработке SSR приложений существуют множество проблем. Проблема асинхронных операций это только одна из них. Давайте посмотрим на другие распространенные проблемы.

HTML Meta Tags для SSR



Немаловажным аспектом в разработке SSR является использование правильных HTML meta tags. Они сообщают поисковому боту ключевую информацию на странице.
Для реализации данной задачи рекомендую использовать один из модулей:
React-Helmet-Async
React-Meta-Tags
Я подготовил несколько примеров:
React-Helmet-Async
React-Meta-Tags

Dynamic Imports


Чтобы снизить размер финального бандла приложения, принято приложение делить на части. Например dynamic imports webpack позволяет автоматически разбить приложение. Мы можем вынести отдельные страницы в чанки или блоки. При SSR мы должны уметь обрабатывать данные фрагменты приложения как одно целое. Для этого рекомендую использовать замечательный модуль @loadable

Dummies


Некоторые компоненты или фрагменты страницы можно не рендерить на сервере. Например, если у вас есть пост и комментарии, не целесообразно обрабатывать обе асинхронные операции. Данные поста более приоритетны чем комментарии к нему, именно эти данные формируют SEO нагрузку вашего приложения. По этому мы можем исключать не важные части при помощи проверок типа:
if (typeof windows === 'undefined') {}


localStorage, хранение данных


NodeJS не поддерживает localStorage. Для хранения сессионных данных мы используем cookie вместо localStorage. Файлы cookie отправляются автоматически по каждому запросу. Файлы cookie имеют ограничения, например:
  • Файлы cookie это старый способ хранения данных, они дают ограничение в 4096 байт (фактически 4095) на один файл cookie.
  • localStorage это реализация интерфейса хранилища. Он хранит данные без даты истечения срока действия и очищается только с помощью JavaScript или очистки кеша браузера / локально сохраненных данных в отличие от истечения срока действия файлов cookie.


Некоторые данные должны быть переданы в URL. Например, если мы используем локализацию на сайте, то текущий язык будет частью URL. Такой подход улучшит SEO, поскольку у нас будут разные URL-адреса для разных локализаций приложения и обеспечить передачу данных по запросу.

React Server Components


React Server Components возможно будет хорошим дополнением для SSR. Его идеей является снижение нагрузки на Bundle за счет выполнения компонент на сервере и выдачи готового JSON React дерева. Нечто подобное мы видели в Next.JS. Читайте подробнее по ссылке

Роутинг


React Router из коробки поддерживает SSR. Отличие в том, что на server используется StaticRouter с переданным текущим URL, а на клиенте Router определяет URL автоматически при помощи location API. Пример

Debugging


Дебаг на сервере может выполняться также как и любой дебаг node.js приложений через inpsect.
Для этого нужно добавить в webpack.config для nodejs приложения:
devtool: 'source-map'
А в настройки NodemonPlugin:
new NodemonPlugin({  watch: path.resolve(__dirname, './dist'),  nodeArgs: [    '--inspect'  ]})

Также, для улучшения работы с source map можно добавить модуль
npm install source-map-support --save-dev

В nodeArgs опций NodemonPlugin добавить:
--require=source-map-support/register
Пример

Next.JS


Если вы создаете приложение с нуля, рекомендую обратить внимание на данный фреймверк. Это самое популярное решение на данный момент для создания с нуля приложений с поддержкой SSR. Из плюсов можно выделить то, что все идет из коробки (билд система, роутер). Из минусов необходимо переписывать существующее приложение, использовать подходы Next.JS.

SEO это не только SSR!



Критерии Google бота для SEO оптимизации включают множество метрик. Рендер данных, получение первого байта и т.д. это лишь часть метрик! При SEO оптимизации приложения необходимо минимизировать вес картинок, бандла, грамотно использовать HTML теги и HTML мета теги и прочее.
Для проверки вашего сайта при SEO оптимизации можно воспользоваться:
lighthouse
sitechecker
pagespeed

Выводы


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

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru