Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру 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 основное внимание уделено так называемому «пререндерингу», заблаговременному, предварительному рендерингу страниц, что, в целом, представляет собой комбинацию CSR и SSR. Страницы после пререндеринга представляют собой HTML-скелеты, готовые к наполнению данными с помощью AJAX/HXR. Когда страница загружается, внутренние задачи маршрутизации решаются динамически, что позволяет пользоваться полезными возможностями клиентского рендеринга.
При пререндеринге используются SSR и технологии генерирования статических сайтов (Static-Site Generation, SSG). SSG, что очень похоже на SSR, это — заблаговременный рендеринг всех HTML-страниц во время сборки веб-проекта. А это значит, что в таком проекте нет нужды пользоваться клиентским рендерингом.
Среди сильных сторон веб-приложений, в которых применяется пререндеринг, можно отметить тот факт, что их сборка и взаимодействие с ними клиентов — это процессы, разнесённые во времени. В результате, даже если сборка таких приложений занимает достаточно много времени, это никак не влияет на пользовательский опыт. После того, как SSG-страницы полностью готовы к работе, они выгружаются на CDN-ресурсы. А это значит, что они после этого могут быть очень быстро и эффективно загружены пользователями. И наконец, так как SSG-страницы попадают к пользователям с CDN-ресурсов, пользователь никак не взаимодействует с сервером, да и сам сервер при таком подходе оказывается ненужным.
До того, как данные начали на постоянной основе хранить на стороне клиента, они хранились только на серверах и там же, по запросу клиента, обрабатывались. То есть — каждый раз, когда приложению нужно было как-то воспользоваться данными — вывести их на странице, обновить или куда-то передать, нужно было перерендерить то, что выводится в браузере. Первой реализацией постоянного хранения данных на клиенте были куки-файлы. Спецификация, в которой они описаны, создана Лу Монтулли на самой заре становления веба.
Каждый запрос данных у сервера, выполняемый браузером, подразумевает передачу куки-файлов. В результате, так как эти файлы постоянно пересылаются между сервером и браузером, лучше не использовать их для хранения хоть сколько-нибудь значительных объёмов информации, так как это может привести к перегрузке канала связи между клиентом и сервером.
Современные браузеры поддерживают API Web Storage, которое позволяет хранить данные на стороне клиента в формате ключ-значение. В частности, им доступно локальное хранилище данных (
Кроме того, содержимое локального хранилища не отправляют на сервер при выполнении каждого запроса к нему. Веб-сервер не имеет возможности напрямую обращаться к локальному хранилищу.
В то время как фреймворк Next.js позволяет весьма эффективно пользоваться технологиями SSR и SSG, в нём не предусмотрено интуитивно понятного механизма для организации постоянного хранения данных на клиенте. Именно тут нам на помощь приходит next-persist.
Итак, что собой представляет пакет next-persist и как он связан с Next.js и с организацией постоянного хранения данных на клиенте? Как уже было сказано, это — компактный и нетребовательный к ресурсам NPM-пакет, созданный ради упрощения работы с данными, которые на постоянной основе хранятся на клиенте и не имеют критического значения. При этом next-persist задуман так, чтобы его применение не ухудшило бы полезных SSR- и SSG-возможностей Next.js.
Возьмём, для примера, чей-то личный блог. Если он построен с использованием Next.js, то, пожалуй, его создателю не помешала бы комбинация сильных сторон этого фреймворка и возможностей, связанных с постоянным хранением на клиенте динамического состояния приложения. И всё это — без изменения архитектуры проекта и без дополнительных трат, связанных с применением системы управления базами данных.
Пакет next-persist позволяет всего этого добиться. Это — простое решение, применимое в динамических, изоморфных веб-приложениях. Для организации постоянного хранения данных приложения на клиенте достаточно импортировать в него next-persist, быстро кое-что настроить и включить в код приложения вызовы соответствующих функций. Всё остальное — забота next-persist, который, не отбирая у владельца проекта полезных возможностей серверного рендеринга, даёт ему механизмы для постоянного хранения данных на клиенте.
Для начала вам понадобится Next.js-приложение, код которого был создан в вашем любимом текстовом редакторе или в IDE, которая вам нравится.
Установим next-persist, выполнив следующую команду в терминале:
Импортируем
Если для организации постоянного хранения данных на клиенте планируется использовать
Если данные планируется хранить с помощью куки-файлов — надо, на верхнем уровне Next.js-приложения, импортировать
Пакет next-persist перед использованием нужно настроить. Для этого используется объект с параметрами, который позволяет адаптировать поведение пакета под конкретный проект. Во-первых, в этом объекте должен присутствовать обязательный ключ
Параметр
Если нужно хранить все данные из редьюсера, то в ключе
В каждом файле редьюсера нужно импортировать
Далее, надо объявить константу и присвоить ей значение, возвращённое после вызова метода
Эту константу надо передать редьюсеру в качестве стандартного параметра, используемого для описания состояния:
Применение метода хранения данных, основанного на куки-файлах, даёт полезную возможность работы с состоянием клиентского приложения с использованием
В этом примере мы вызываем метод
В этом материале мы обсудили проблему Next.js, связанную с организацией постоянного хранения данных на клиенте, и представили инструмент, направленный на решение этой проблемы — пакет next-persist. Надеемся, он вам пригодится. Вот репозиторий пакета, а вот — его сайт.
Планируете ли вы использовать next-persist в своих проектах?
Прежде чем мы поговорим о месте 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 в своих проектах?