Библиотека next-persist: преодоление разрыва между серверным рендерингом и постоянным хранением данных на клиенте

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Статья, перевод которой мы публикуем сегодня, посвящена next-persist — компактному и нетребовательному к ресурсам NPM-пакету. Цель его создания — упрощение обработки и реконсиляции данных, на постоянной основе хранящихся на клиенте и не имеющих критического значения. При этом данный пакет задуман так, чтобы его применение не ухудшило бы полезных возможностей Next.js по серверному рендерингу и генерированию статических сайтов.

Прежде чем мы поговорим о месте next-persist в экосистеме современной веб-разработки, нам нужно коснуться Next.js — передового фреймворка, созданного Vercel, позволяющего без особых сложностей создавать и развёртывать приложения, которые, при этом, отличаются высокой скоростью загрузки.



Next.js произвёл революцию в осмыслении того, как именно веб-содержимое доставляется пользователям. Произошло это за счёт объединения всего лучшего из сфер серверного и клиентского рендеринга с технологиями генерирования статических сайтов. Кроме того, применение Next.js до крайности упрощает доведение веб-проектов до рабочего состояния. Это происходит за счёт автоматизации конфигурирования вспомогательного ПО, вроде webpack и babel, и благодаря использованию CI/CD Vercel.

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

Что такое серверный рендеринг?


Серверный рендеринг (Server-Side Rendering, SSR) — это когда веб-страницы, по запросу, создаются на сервере, но при этом имеется возможность хранить на сервере динамические пользовательские данные и отдавать их клиенту. Применение этой технологии означает, что пользователь не столкнётся с задержкой, вызванной интенсивной передачей данных, необходимых для сборки страницы, между клиентом и сервером.

Среди сильных сторон SSR можно отметить тот факт, что, так как браузеры обычно чрезвычайно эффективны в деле рендеринга HTML-разметки, генерируемой сервером, содержимое веб-страниц, запрошенное у сервера, практически мгновенно готово к выводу на экран. Браузеру, для вывода страниц, не нужно ничего, кроме HTML-кода, полученного от сервера. Применение SSR означает отсутствие проблем с поисковой оптимизацией (Search Engine Optimization, SEO), так как поисковые роботы анализируют готовый HTML-код, получаемый ими непосредственно с сервера.

Недостаток SSR заключается в том, что сервер вынужден создавать страницы при отправке ответа на каждый полученный им от клиента запрос. При таком подходе сервер, в перспективе, будет выполнять массу тяжёлой работы, так как он принимает участие в формировании каждой страницы, что может довольно сильно его нагрузить. Ещё один минус SSR заключается в том, что сервер всегда, отвечая на каждый поступивший к нему запрос, генерирует HTML-код. При этом, например, ничего не кешируется на уровне сетей доставки контента (Content Delivery Network, CDN).

Что такое клиентский рендеринг?


Клиентский рендеринг (Client-Side Rendering, CSR) — это когда для приведения страниц в рабочее состояние используются ресурсы браузера. В частности — клиентские JavaScript-программы. При применении CSR HTML-страницы не создаются заранее. Страницы динамически генерируются в браузере.

Среди преимуществ этого подхода к веб-разработке можно отметить тот факт, что JavaScript-бандлы, создаваемые инструментами вроде create-react-app (CRA) могут размещаться на CDN-ресурсах. Это значит, что HTML-страница находится довольно близко к пользователю, что позволяет пользователю достаточно быстро и просто её загрузить. Кроме того, применение CSR означает, что в начале работы браузеру не приходится взаимодействовать с сервером конкретного веб-проекта. В начале работы всё взаимодействие ограничивается обменом данными между браузером и CDN-ресурсом.

У клиентского рендеринга веб-страниц есть и недостатки. В частности, браузер не сможет показать пользователю ничего полезного до тех пор, пока не будет обработан весь JavaScript-код, ответственный за создание виртуальной DOM. Кроме того, применение CSR означает и появление некоторых проблем в сфере SEO. Поисковые роботы, загружающие CSR-сайты, видят лишь пустые страницы, в состав которых включены JavaScript-бандлы.

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

Next.js + SSR + SSG


В Next.js основное внимание уделено так называемому «пререндерингу», заблаговременному, предварительному рендерингу страниц, что, в целом, представляет собой комбинацию CSR и SSR. Страницы после пререндеринга представляют собой HTML-скелеты, готовые к наполнению данными с помощью AJAX/HXR. Когда страница загружается, внутренние задачи маршрутизации решаются динамически, что позволяет пользоваться полезными возможностями клиентского рендеринга.

При пререндеринге используются SSR и технологии генерирования статических сайтов (Static-Site Generation, SSG). SSG, что очень похоже на SSR, это — заблаговременный рендеринг всех HTML-страниц во время сборки веб-проекта. А это значит, что в таком проекте нет нужды пользоваться клиентским рендерингом.

Среди сильных сторон веб-приложений, в которых применяется пререндеринг, можно отметить тот факт, что их сборка и взаимодействие с ними клиентов — это процессы, разнесённые во времени. В результате, даже если сборка таких приложений занимает достаточно много времени, это никак не влияет на пользовательский опыт. После того, как SSG-страницы полностью готовы к работе, они выгружаются на CDN-ресурсы. А это значит, что они после этого могут быть очень быстро и эффективно загружены пользователями. И наконец, так как SSG-страницы попадают к пользователям с CDN-ресурсов, пользователь никак не взаимодействует с сервером, да и сам сервер при таком подходе оказывается ненужным.

Постоянное хранение данных на клиенте


До того, как данные начали на постоянной основе хранить на стороне клиента, они хранились только на серверах и там же, по запросу клиента, обрабатывались. То есть — каждый раз, когда приложению нужно было как-то воспользоваться данными — вывести их на странице, обновить или куда-то передать, нужно было перерендерить то, что выводится в браузере. Первой реализацией постоянного хранения данных на клиенте были куки-файлы. Спецификация, в которой они описаны, создана Лу Монтулли на самой заре становления веба.

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

Современные браузеры поддерживают API Web Storage, которое позволяет хранить данные на стороне клиента в формате ключ-значение. В частности, им доступно локальное хранилище данных (localStorage), точный размер которого зависит от браузера. В локальном хранилище, как и в куки-файлах, данные хранятся даже после закрытия браузера.

Кроме того, содержимое локального хранилища не отправляют на сервер при выполнении каждого запроса к нему. Веб-сервер не имеет возможности напрямую обращаться к локальному хранилищу.

В то время как фреймворк Next.js позволяет весьма эффективно пользоваться технологиями SSR и SSG, в нём не предусмотрено интуитивно понятного механизма для организации постоянного хранения данных на клиенте. Именно тут нам на помощь приходит next-persist.

Знакомство с next-persist


Итак, что собой представляет пакет next-persist и как он связан с Next.js и с организацией постоянного хранения данных на клиенте? Как уже было сказано, это — компактный и нетребовательный к ресурсам NPM-пакет, созданный ради упрощения работы с данными, которые на постоянной основе хранятся на клиенте и не имеют критического значения. При этом next-persist задуман так, чтобы его применение не ухудшило бы полезных SSR- и SSG-возможностей Next.js.

Возьмём, для примера, чей-то личный блог. Если он построен с использованием Next.js, то, пожалуй, его создателю не помешала бы комбинация сильных сторон этого фреймворка и возможностей, связанных с постоянным хранением на клиенте динамического состояния приложения. И всё это — без изменения архитектуры проекта и без дополнительных трат, связанных с применением системы управления базами данных.

Пакет next-persist позволяет всего этого добиться. Это — простое решение, применимое в динамических, изоморфных веб-приложениях. Для организации постоянного хранения данных приложения на клиенте достаточно импортировать в него next-persist, быстро кое-что настроить и включить в код приложения вызовы соответствующих функций. Всё остальное — забота next-persist, который, не отбирая у владельца проекта полезных возможностей серверного рендеринга, даёт ему механизмы для постоянного хранения данных на клиенте.

Использование next-persist в Next.js-приложениях


Для начала вам понадобится Next.js-приложение, код которого был создан в вашем любимом текстовом редакторе или в IDE, которая вам нравится.

Установим next-persist, выполнив следующую команду в терминале:

npm install next-persist

Импортируем <NextPersistWrapper /> во фронтенд-код на верхнем уровне Next.js-приложения:

// _app.jsx
import PersistWrapper from 'next-persist/src/NextPersistWrapper';

Если для организации постоянного хранения данных на клиенте планируется использовать localStorage — надо импортировать в редьюсер (или в редьюсеры) { getStorage }:

// yourReducer.jsx
import { getStorage } from 'next-persist'

Если данные планируется хранить с помощью куки-файлов — надо, на верхнем уровне Next.js-приложения, импортировать { getCookie }:

// _app.jsx
import { getCookie } from 'next-persist/src/next-persist-cookies'

Настройка next-persist


Пакет next-persist перед использованием нужно настроить. Для этого используется объект с параметрами, который позволяет адаптировать поведение пакета под конкретный проект. Во-первых, в этом объекте должен присутствовать обязательный ключ method, указывающий пакету на то, какой именно метод хранения данных нужно использовать. Второй параметр, необязательный — это объект allowList.

//_app.js
  const npConfig = {
    method: 'localStorage' // или 'cookies'
    allowList: {
      reducerOne: ['stateItemOne', 'stateItemTwo'],
      reducerTwo: [],
    },
  };

Параметр allowList позволяет разрешить указанным в нём редьюсерам хранить определённые фрагменты состояния приложения с использованием выбранного метода хранения данных. Ключи в объекте allowList должны соотноситься с ключами редьюсеров в combineReducers(). Для настройки хранения лишь отдельных фрагментов состояния приложения нужно внести строковые имена этих фрагментов в соответствующий массив.

Если нужно хранить все данные из редьюсера, то в ключе allowList, который соответствует этому редьюсеру, хранится пустой массив. Если в объекте с параметрами не будет ключа allowList, это означает, что next-persist будет хранить все данные состояния приложения из всех редьюсеров с использованием выбранного метода хранения данных.

Обёртка


<PersistWrapper wrapperConfig={YOUR CONFIG HERE}/> принимает одно свойство, именем которого обязательно должно быть wrapperConfig. Это свойство содержит объект с параметрами, заранее созданный разработчиком в компоненте _app (этот объект может иметь любое имя):

import "../styles/globals.css";
import { Provider } from "react-redux";
import store from "../client/store";
import PersistWrapper from 'next-persist/src/NextPersistWrapper';
const npConfig = {
    method: 'localStorage' or 'cookies'
    allowList: {
      reducerOne: ['stateItemOne', 'stateItemTwo'],
      reducerTwo: [],
    },
  };
const MyApp = ({ Component, pageProps }) => {
  console.log('MyApp pageProps: ', pageProps);
  return (
    <Provider store={store}>
      <PersistWrapper wrapperConfig={npConfig}>
        <Component {...pageProps} />
      </PersistWrapper>
    </Provider>
  );
};
export default MyApp;

Редьюсеры


В каждом файле редьюсера нужно импортировать getStorage из 'next-persist' или getCookies из 'next-persist/src/next-persist-cookies'.

Далее, надо объявить константу и присвоить ей значение, возвращённое после вызова метода getStorage или getCookies. Эти методы принимают два аргумента:

  • Строка: ключ редьюсера, данные которого размещаются в хранилище.
  • Объект: исходное состояние, описанное в файле редьюсера.

Эту константу надо передать редьюсеру в качестве стандартного параметра, используемого для описания состояния:

import * as types from '../constants/actionTypes';
  import { getStorage } from 'next-persist';
  // или
  // import { getCookies } from 'next-persist/src/next-persist-cookies'

  const initialState = {
    //тут настраивается исходное состояние
    stateItemOne: true,
    stateItemTwo: 0,
    stateItemThree: 'foo',
  };

  const persistedState = getStorage('reducerOne', initialState);
  // или
  // const persistedState = getCookies('reducerOne', initialState);

  const firstReducer = (state = persistedState, action) => {
    // логика установки состояния, основанная на анализе типа действия
    switch (action.type) {
    default:
      return state;
    }
  };

  export default firstReducer;

Использование куки-файлов


Применение метода хранения данных, основанного на куки-файлах, даёт полезную возможность работы с состоянием клиентского приложения с использованием getInitialProps. Но при таком подходе, из-за ограничений размеров куки-файлов, на клиенте нельзя хранить большие объёмы информации.

В этом примере мы вызываем метод getCookie в getInitialProps и возвращаем объект с данными, размещёнными в нём с использованием ключа, имя которого совпадает с именем соответствующего редьюсера:

  MyApp.getInitialProps = async (ctx) => {
    const cookieState = getCookie(ctx);
    return {
      pageProps: cookieState,
    };
  }
  export default MyApp;

Итоги


В этом материале мы обсудили проблему Next.js, связанную с организацией постоянного хранения данных на клиенте, и представили инструмент, направленный на решение этой проблемы — пакет next-persist. Надеемся, он вам пригодится. Вот репозиторий пакета, а вот — его сайт.

Планируете ли вы использовать next-persist в своих проектах?

Источник: https://habr.com/ru/company/ruvds/blog/554156/


Интересные статьи

Интересные статьи

В СУБД растут объемы данных и нагрузки. А это значит, что нужно держать нос по ветру и узнавать больше об инструментах и методах взаимодействия с базами данных. Чтобы это...
От переводчика: свой путь на habr я решил начать не с попытки написать какой-то уникальный текст с нуля, а с перевода относительно свежей (от 17.08.2020) статьи классика PL/SQL-разработки...
В этой части статьи мы познакомимся с инструментами, которые позволяют задавать и редактировать параметрические зависимости взаимного расположения 3D-тел. А также мы расс...
В прошлой статье мы описали эксперимент по определению минимального объема вручную размеченных срезов для обучения нейронной сети на данных сейсморазведки. Сегодня мы продолжаем эту тему, выбирая...
Сегодня мало кто помнит, что веб-приложения могут работать без единого XHR-запроса. AJAX (Asynchronous Javascript and XML) дает классную возможность — подгружать данные без перезагрузки стран...