Два года назад команда Next.js представила новый подход к роутингу, который должен был стать заменой так называемому Pages Router, вместе с тем добавив ряд принципиально нового функционала.
Практически в каждом релизе я находил множество полезного и нужного как для личных проектов, так и для коммерческих. Тем не менее, 13-ю версию я обошёл стороной для коммерческих проектов, так как функционал показывал себя крайне нестабильным и недостаточным. Однако, сейчас этот функционал перенесён в категорию стабильных, App Router считается основным, а Pages Router скорее поддерживаемым для обратной совместимости и постепенного перехода.
Next.js сделал крупный шаг, взяв на себя ответственность за кеширование и работу с запросами, добавив серверные компоненты, введя параллельные и перехватываемые роуты, а также ряд других абстракций. В этой статье речь пойдёт о причинах этого шага, возможностях, проблемах и личном мнении – был ли этот шаг в будущее или же шаг прямиком в яму.
Ретроспектива
Перед тем, как погружаться в последнее обновление, стоит описать немного ретроспективы. Я использую next.js с 8-й версии, уже порядка пяти лет и внимательно следил за всеми последующими обновлениями, залезаю к нему под капот для понимания (и изредка исправления) проблем и делаю дополнительные незначительные пакеты. Мои впечатления от новой версии повторяются практически всегда – от “что это за ужас” и “кто это наделал!”, до “это ведь гениально!” и обратно по кругу.
Что я точно понял за эти годы – next.js нельзя назвать идеалом стабильности. Если функционал перешёл в категории стабильных – значит стоит подождать ещё пару версий до того, как будут исправлены основные его ошибки (если повезёт). Я столкнулся с десятком багов, некоторые из которых становились причиной отката месяцев работы команды, а некоторые плотным основанием для временных костылей (ну, вы сами понимаете).
Количество исправленных багов даже стало пунктом последних релизов.
Untitled
По поводу стабильности Next.js недавно была опубликована интересная статья одного из разработчиков Remix (на данным момент бывшего) Кента Доддс, на которую впоследствии ответил Вице-Президент Vercel – Ли Робинсон. В конце статьи будет личное мнение об этом споре.
Фреймворк очень активно развивался все эти годы. Частые крупные обновления привели к появлению большого количества багов, но вместе с тем и к быстрому внедрению очень полезного и перспективного функционала (ведь нет лучше тестовой среды, чем прод).
Например, с появлением реврайтов и редиректов стало возможно отказаться от частых изменений nginx, с внедрением middleware – переделать логику обработки роутов и улучшить возможности а/б тестов. С появлением ISR – сделать возможность обновления страниц без перевыкладки сервиса, с его улучшением (on-demand ISR) – значительно упростить.
Next.js сегодня
С каким бы количеством проблем я не сталкивался, next.js продолжает быть самым технологичным фреймворком. Это множество полезного функционала, которое покрывает абсолютное большинство потребностей. И с каждым обновлением эта зона покрытия лишь растёт (именно в части не размера, не качества).
Самым главным обновлением последних версий, пожалуй, можно назвать серверные компоненты, даже несмотря на то, что это разработка команды react.js.
Серверные компоненты дают возможность выполнять сложную логику на этапе сборки без дополнительных надстроек. С ними не обязательно прокидывать крупные пакеты на клиент, заморачиваться с секретами или оптимизировать переводы – это будет ответственность сервера.
Несмотря на всю полезность серверных компонент, в них есть один огромный минус для использования в реальных проектах – отсутствие контекстов, из-за чего данные приходится прокидывать на десятки уровней вложенности, в том числе пути и параметры страницы. Частично это удалось обойти сделав пакет next-impl-getters, но, конечно, такой функционал не должен существовать отдельно от фреймворка.
Помимо участия в разработке серверных компонент, Next.js развивает и поддерживает десятки своих решений, и если говорить о 14-й версии, то это именно App Router – новый подход в настройке роутинга, который комбинирует возможности сборки, сервера и рантайма.
App Router
Важной особенностью Next.js является его роутинг – а точнее, использование файловой системы в качестве настройки этого роутинга. Прежде всё в этом было просто – по какому пути лежит страница, по такому пути она и будет доступна на сайте. В новой же версии роутинг стал сложнее и первое, что нарушает эту логику – группы.
Группы
Группы позволяют объединять страницы по какому-либо признаку. Это позволяет, например, создать страницам на одном уровне разные слои.
Источник: https://nextjs.org/docs
Слои и шаблоны
Слои являются некой обёрткой для всех страниц ниже в директории – это альтернатива прежним абстракциям _app и _document. Важной их особенностью является то, что они не ререндерятся при переходах между страниц. В этом же и их минус – если общий слой имеет хоть какое-то различие в зависимости от страниц – использовать эту абстракцию не удастся.
Источник: https://nextjs.org/docs
В отличие от слоёв, шаблоны перестраиваются каждый раз. Однако в них нельзя получить динамические параметры (пакет решает эту проблему, но опять же это должно быть из коробки). Как итог, обе этих абстракции покрывают далеко не все случаи и приходится добавлять общий компонент на все страницы.
Параллельные роуты
Параллельные роуты – это механизм, который позволяет загружать несколько самостоятельных и незавимых слотов в одной странице. Они могут иметь свои шаблоны, обработчики ошибок и загрузки.
Источник: https://nextjs.org/docs
Перехватываемые роуты
Перехватываемые роуты используются в связке с параллельными. Они дают возможность при переходе пользователя с одной страницы на другую, “перехватить” её и вместо загрузки полноценной страницы отобразить что-то другое, например при переходе на изображение перехватить это и отобразить модалку с ним, а после перезагрузки показывать полноценное изображение.
Источник: https://nextjs.org/docs
Другие абстракции
Для директорий добавили возможность настраивать также ошибки и загрузчики (и в них тоже недоступны динамические параметры). Это работает как с полноценными страницами, так и с параллельными роутами.
Источник: https://nextjs.org/docs
Работа с данными и запросами
Другим заслуживающим внимание изменением является переделка fetch (очередная). Теперь вызывая fetch, вызывается обёртка от next.js, которая модифицирует запрос, обрабатывает его и кеширует результат. Всё это происходит инкрементально, то есть при запросе сразу возвращается последний сохранённый ответ, а в фоне выполняется запрос за актуальными данными.
Во многом переделка fetch связана с тем, что во время сборки Next.js собирает параллельно десятки страниц, которые, зачастую, вызывают одни и те-же запросы. Раньше для обхода этого нужно было написать свой кастомный класс загрузчика и использовать везде его.
Кеширование же было задействовано и во многих других местах, таких как обработка и пересборка страниц, обработка роутов, редиректов и реврайтов, определение статуса запроса и др.
Я не знаю, почему в последнее время библиотеки стали брать на себя лишнюю ответственность – будь то кеширование Next.js или работа с формами React.js. Это странная попытка зафиксировать то, где использовались сотни вариантов со своими особенностями, предполагая, что так будет лучше. В последствии команда Next.js добавила возможность отключать кеширование или настраивать самостоятельно, но только для части функционала.
Выводы
Next.js пополнился возможностями и оптимизациями и теперь покрывает ещё больше ситуаций. Однако без серверных контекстов и динамических параметров во многих абстракциях – потенциал значительно урезан.
Переделка базового API – fetch – и усиленное кеширование дали возможность не переживать о производительности, но всё же только в небольших проектах, в крупных же часто спотыкаешься о недостатки этого решения (напр. закешировался реврайт когда это не нужно, редиректит не туда, лимит на кеширование, возвращение устаревшей страницы, несмотря на отключенное кеширование).
Команда Next.js стала уделять больше внимания багам, но в большей степени это произошло за счёт резкого роста этих багов. В целом же, новый функционал в значительной степени завершён. Другой вопрос – устраивает ли заложенная в него логика и вот здесь однозначного ответа нет. Кому-то эти изменения и их проблемы оказались критическими, поэтому они продолжают использовать Pages Router, кому-то недостаточным и приходится использовать ряд костылей (и я в этой группе), а кому-то эти решения подходят идеально, так как проект не подпадает под проблемные зоны.
Мнение о составляющих спора
Я несколько раз перечитал аргументы Ли Робинсона, но, к своему удивлению, не нашёл ответов на многие вопросы, поэтому, пожалуй, пройдусь по списку проблем от Кента (по которым не нашёл ответа в статье от Ли):
1. “вместо того, чтобы рекомендовать использовать директиву веб-платформы Stale While Revalidate Cache Control, для достижения той же цели они изобрели очень сложную функцию, названную инкрементальная статическая регенерация (ISR)”
Почему это сделано так? Дело в том, что cache-control – заголовок, ответственность за который несёт браузер и, если мы передали stale-while-ravalidate со значением 1 день, то браузер в течении дня у текущего пользователя не будет обновлять закешированное. В парадигме же next.js кэш может обновиться не просто в конкретный момент сразу для всех пользователей, а вообще в любой момент вызвав функционал on-demand ISR. Среди прочего эта логика кеширования распространяется не только на браузер, но и на запросы из сервера или в момент сборки.
В качестве хранилища же используется файловая система, облачное хранилище или кастомная настройка. То есть несмотря на похожесть идей, их логика отличается (и по моему опыту в лучшую сторону в данном контексте).
2. “OpenNext существует, потому что Next.js сложно развернуть где-либо, кроме Vercel”
Сам Next.js развернуть невероятно просто – yarn build, yarn start. На этом всё. Никаких особенностей окружения, никаких секретных зависимостей. OpenNext же пытается повторить возможности Vercel, не более.
При этом не могу не признать, что в next.js есть некоторая зависимость от Vercel. Например выше я упомянул, что в качестве хранилища может быть облачное хранилище. И внутри next.js есть логика на использование облачного хранилища Vercel. Тем не менее, по умолчанию используется файловая система, а при желании можно достаточно легко настроить своё облачное хранилище или, например, подключить Remix (что было предложено в последнем релизе). Таких мест немного, но они есть, в любом случае для всех из них есть вариант по умолчанию, которого более чем достаточно.
3. “похоже, что Vercel пытается стереть грань между тем, что такое Next.js, и тем, что такое React. Люди не понимают, что такое React и что такое Next.js, особенно в отношении серверных компонентов и серверных действий”
Несмотря на огромную эффективность этого сотрудничества, не могу не согласиться, как размывается эта грань между next.js и react – если раньше можно было говорить, что Next.js это тестовая среда для React.js, то сейчас ощущение, что Next.js сами продвигают идеи в react, чтобы использовать их. Именно next.js рассказывают про серверные компоненты и серверные действия, от чего складывается ощущения, что это их разработка. Возможно, скорая React Conf исправит эту ситуацию.
В Vercel были наняты три ключевых разработчика React.js – Andrew Clark, Sebastian Markbåge и Josh Story. Однако о серверных компонентах мы слышим очень давно и начало работ было положено до перехода этих разработчиков в Vercel.
4. “решение переопределить глобальную функцию fetch
для добавления автоматического кэширования. Для меня это огромный красный флаг”
Здесь был ответ от Ли: “В Next.js 14, например, если вы хотите отказаться от кеширования, вы бы использовали noStore() вместо [опции] cache: 'no-store'
у fetch
“.
Но я не понимаю, как это отвечает на проблему. А также не вижу объективных причин, почему реализация Next.js не была вынесена в отдельное API, образное fetchNext()
с линтеровским предупреждением при использовании обычного fetch (также как они поступили с тэгом Image вместо ручных img).
5. Стабильность и Сложность
Стабильности посвящена значительная часть статьи выше. По сложности хочу в очередной раз признать потрясающую документацию next.js, где очень просто искать ответы. Я вижу, как в документацию next.js каждый день предлагают PR буквально даже на незначительную разницу в формулировках, за счёт чего она получается не просто удобной, но и максимально понятной.
Постскриптум
Помимо next-impl-getters я начал работы и над другими замечательными пакетами:
next-impl-config – next.js по сути работает в 4 средах – сборка, сервер, клиент и edge, при том, что конфигурацию описывают только для двух из них – сборка и сервер. Данный же пакет даёт возможность добавить настройки для каждой возможной среды.
next-classnames-minifier – из-за особенностей кеширования next.js сложно настроить сжатие классов до символов (.a, .b, …, .a1) и чтобы решить эту задачу был сделан этот пакет, которому была посвящена недавняя статья.
next-translation – мне никогда особо не нравились существующие решения в контексте next.js и ещё больше они перестали нравится сейчас, с появлением серверных компонент. Данный пакет был спроектирован в первую очередь в расчёте на серверные компоненты и максимальную оптимизацию (за счёт переноса логики на этап сборки и/или серверную сторону).
Monstro – сервис для продвижения сайтов и услуг
https://t.me/monstrotraf