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

в 12:00, , рубрики: javascript, Next-persist, ruvds_переводы, Блог компании RUVDS.com, разработка, Разработка веб-сайтов

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

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

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

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 в своих проектах?

Автор: ru_vds

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js