Опыт первого знакомства с Next.js

072766e17f2b7d4105de2cddfc3b6804

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

На работе мы используем React только на клиенте, поэтому параллельно пришлось изучать еще и новые серверные функции React.

При изучении любой новой технологии я делю весь процесс на следующие этапы:

  1. Ознакомительное чтение документации

  2. Практическое прохождение обучения по шагам документации

  3. Использование новой технологии в своем тренировочном проекте

При чтении документации первым сюрпризом стало то, что есть две параллельные документации.
Дело в том, что до 13 версии в Next использовался Pages Router, а дальше появился AppRouter, при этом Pages Router оставили для обратной совместимости.
В остальном с первыми двумя этапами проблем не возникло. Поэтому подробно опишу только третий этап.

Но сначала в двух словах — что такое Next и зачем он нужен.
Когда-то давно, до появления интерактивности на клиенте сайты работали таким образом, что и логика и представление формировались на бэкенде, затем при запросе с клиента определенной страницы бэкенд отдавал HTML код этой страницы и браузер ее отрисовывал.
Интерактивность на клиенте сводилась либо к переходам по ссылкам, либо к отправке форм.
При отправке форм данные отправлялись на сервер и там обрабатывались, при необходимости выполнялся редирект на страницу с сообщением об успешной отправке.
Когда появилась интерактивность, а затем и эпоха Single Page Application, все действия стали выполняться на клиенте, а взаимодействие с сервером свелось к API запросам.
Таким образом клиент и сервер стали почти независимыми. Минус такого подхода был в том, что первоначальный код, отдаваемый сервером клиенту уменьшился до одного тега, в котором потом разворачивалось приложение на клиенте.
Поисковики либо вообще не индексировали такой контент, либо делали это очень плохо. А для любого сайта это критически важно.
Тогда появился новый подход — код для клиента снова стал приходить с бэкенда, а затем клиентские фреймворки его гидрировали, то есть превращали в интерактивный.
Важно, что при таком подходе между сервером и клиентом появилась прослойка, которая называется Backend For Frontend.
И это означает, что у фронтенд-разработчиков появилась возможность писать код не только для браузера, но и для такого бэкенда.
Фреймворк Next.js как раз и решает эту задачу, разделяя компоненты на клиентские и серверные и при этом снимает с программиста вопрос как это всё синхронизировать.
Вместо REST API для взаимодействия с бэкендом Next использует внутренний механизм RSC Payload для передачи данных небольшими порциями (чанками).

У меня есть приложение с простым функционалом: список записей, страница каждой записи, страница логина, отображение текущего пользователя в тулбаре и логаут.
Для авторизированных пользователей есть возможность создавать и редактировать или удалять свои записи, то есть обычное CRUD приложение с авторизацией.
Поскольку на работе у меня используется только клиент, было интересно проверить получится ли использовать Next только как клиентский фреймворк и будет ли от этого польза. Поэтому я реализовал приложение тремя разными способами:

  1. Только клиентские компоненты

  2. Только серверные компоненты

  3. Максимально серверные компоненты и при необходимости клиентские. Это позволило взглянуть на функциональность фреймворка с разных сторон и глубже изучить его.

Перевести всё на клиентские компоненты было сравнительно не трудно. Для начала пришлось прописать в начале каждого файла 'use client'. При реализации первым открытием стало то, что стейт менеджер на клиенте не нужен. Ни Redux, ни useState с контекстом, вообще ничего!
Всё хранится в базе данных на сервере, любое обращение к данным — это прямое обращение в базу. Для оптимизации всё очень жестко кэшируется.

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

Далее, я попробовал сделать вообще всё на серверных компонентах. Что-то вроде того, как раньше писали сайты на чистом PHP.
И на этом этапе я столкнулся с наибольшим количеством проблем.

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

  1. Server actions. Функции, которые вызываются при отправке форм.

  2. App router. Просто в файловой структуре создаете папку (например items), в ней файл page.tsx и у вас уже автоматически работает адрес /items/.

  3. Route handlers. Аналогично предыдущему пункту создаете файл route.ts, но это будет уже не страница приложения, а некий endpoint, возвращающий json.

  4. Middleware. Можно перехватить любой запрос на сервер, выполнить свои действия и продолжить обработку запроса или остановить ее.

Возьмем простую задачу — при клике на ссылку нужно сделать логаут. Server actions не подходят, т.к. нам не нужна отправка формы.
Можно сделать отдельную страницу с помощью App router и обработать запрос в ней, но это не очень хорошо архитектурно, т.к. логаут это все-таки не страница.
Можно обработать запрос в Middleware, поначалу я так и сделал. Минус в том, что файл middleware при таком подходе разрастается с кучей if’ов (для каждого запроса).
Лучшим решением становится Route handlers. Создается специальный endpoint в который уходит запрос, удаляется сессия пользователя и выполняется редирект.
Важно учитывать, что в серверных компонентах нельзя использовать обработчики событий, например повесить атрибут onclick в ссылке, чтобы вызвать confirm. Приходится либо отказываться от этого функционала, либо делать компонент клиентским.
С клиентской валидацией полей в форме та же проблема — никаких onclick, onchange, onsubmit. Всё только в серверных экшнах.
Интересным открытием стало то, что с куками можно работать только в роутах и экшнах, но нельзя в middleware!
При использовании Next нужно очень внимательно следить за тем, чтобы код, передаваемый с сервера совпадал с кодом на клиенте, иначе появляются ошибки регидрации.
В частности, у меня было много ошибок, связанных с использование Font Awesome иконок. С сервера передавался код, а на клиенте он превращался в svg. В качестве решения предлагалось либо с сервера передавать svg, либо использовать костыли в виде useState с флагом isClient.

Но самой-самой большой болью стала попытка использовать библиотеку NextAuth.js (Auth.js) для авторизации.
В документации Next приведен пример использования этой библиотеки, но в документации NextAuth.js вообще все по-другому.
Оказалось дело в том, что на текущий момент актуальная версия библиотеки 4, а в документации Next описана пятая!!! версия.
Усугубляется это тем, что документации пятой версии на сайте библиотеки вообще нет. Я убил несколько вечеров пока совершенно случайно не нашел, что пятая версия NextAuth.js называет Auth.js и имеет отдельный сайт с отдельной документацией!

При использовании самого Auth.js тоже все печально. Например, есть текущая сессия, в которой хранится объект с данными пользователя. Там мало информации — из нужного мне был только email.
Мне надо было добавить туда id пользователя. Есть специальный метод для этого, он он почему-то добавляет не в объект пользователя, в некий token.sub — это вообще нигде не описано в документации!

Худо-бедно, мне как-то удалось заставить все это работать без клиентских компонентов, но от значительной части функционала пришлось отказаться.
Следующим шагом я начал возвращать этот функционал, добавляя клиентские компоненты.
В первую очередь сделал в формах отображение ошибок и отображение состояния отправки. В интернете много примеров как реализовать isLoading и errors с помощью useActionState, useFormStatus.
Первое, что мне здесь пришлось уяснить, это то, что указанные хуки — это не часть функционала Next, а новые хуки самого React.
А дальше началось веселье. Дело в том, что в документации Next описано использование хука useActionState, однако этот хук появился только в 19 версии реакта, в то время как последняя версия react-dom восемнадцатая. И чтобы все корректно работало нужно использовать либо 18 версию обеих библиотек, либо канареечные версии.
А в 18 версии этот хук находится не в библиотеке react, а в библиотеке react-dom под названием useFormState.
И опять же без сюрпризов со стороны Auth.js не обошлось. Там очень странная релизация клиентского API, например есть метод логаута, но фактически он просто редиректит на роут логаута, который ты должен реализовать сам!

Итоги

Обычно фреймворки создаются как готовый продукт для решения типовых задач, снимая с разработчика низкоуровневые проблемы подбора библиотек, их версий и т.д.
Текущая версия Next очень сырая в этом плане: параллельные реализации как самого Next, так и его библиотек, использование канареечных API в официальной документации, множество открытых issues и т.д.
Не понравилась сама архитектура проекта, в нем все компоненты перемешаны — пока не зайдешь внутрь не поймешь где клиентские, а где серверные. Усугубляется тем, что внутри одного файла может быть одновременно клиентский и серверный код.
Использование серверного кода переводит проект на другой уровень девопса. Это уже не просто бандл, который можно подменить как статику на сервере.
Несмотря на то, что фреймворк в первую очередь предназначен для ускорения интерфейсов в браузере, для разработчиков в режиме отладки работает все очень медленно (если только у вас не новенький мак).

Что понравилось?
Понравилось соответствие структуры папок роутам — положил файл в папку и получил браузерную ссылку.
Понравились Server Actions — это же так интуитивно писать:

Понравилось отсутствие стейт-менеджеров и прямые запросы в базу. Правда ценой новых проблем с инвалидацией кэша.

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

Код можно посмотреть здесь https://github.com/Mirantus/neo-next

Habrahabr.ru прочитано 771 раз