Привет, Хабр!
Как могут заметить разработчики, фреймворк Next очень активно развивается. Так, некоторое время назад в 13 версии появилась новая парадигма (модель) для создания приложений — app router, которая должна прийти на смену старой pages router.
В этой статье мы постараемся наглядно продемонстрировать и рассказать, что же поменялось в работе приложения с появлением app router, какие изменения произошли в сравнении с pages router, что нового успели добавить разработчики, а от чего они отказались.
Изменение структуры проекта
Начнем со структуры самого проекта. В проекте с pages-роутингом в папке src у нас
была папка pages, в которой находились все страницы нашего приложения. В ней лежала
главная страница index и при необходимости добавления новых, нужно было создать одноименную папку и файл index внутри. Стили по дефолту, как и фавиконка, лежали в отдельных папках.
В app-роутинге убрали папку pages, теперь главная страница поменяла свое название с index на page и лежит в папке app рядом со стилями, фавиконкой и layout, речь о котором пойдет позже.
Для добавления же новой страницы, прямо в папке app мы так же должны создать одноименную будущему роутингу папку, внутри которой будет находиться файл с нашей страницей — page.
При этом важно отметить, что если в папке src мы создадим папку pages (по аналогии с работой старого роутера) и создадим там новый файл (например, test.tsx), то у нас будет создана страница с роутом /test. Таким образом, в одном приложении мы можем использовать как новый, так и старый подходы.
Работа с Метаданными
Метаданные помогают нам при работе с SEO и поисковой оптимизации нашего веб-приложения, а также служат инструментом добавления дополнительной информации, стилей, изображений и прочего.
В pages-роутинге работа с метаданными была реализована через тег <Head>. Внутри него мы задаем теги с нужными нам параметрами, например, <title> или <link>. Существует так же тег <meta> для работы, например, с description или open graph. Тег <Head> можно задать на каждой странице приложения.
В app-роутинге задать метаданные мы можем с помощью metadata (объект или функция), которую мы должны экспортировать. Тип у константы будет Metadata, который включает в себя такие параметры как metadataBase, title, description, authors, generator, keywords и так далее. Метаданные мы можем задать как в файле layout, так и в файле нашей странички.
Layout, _app и _document
Когда мы разрабатываем приложение, то нередко сталкиваемся с тем, что некоторые компоненты повторяются у нас на каждой (или почти каждой) странице. Например, хедер, футер, панель навигации. Для того, чтобы заново не отрисовывать компоненты каждый раз при переходе с одной страницы на другую, повысить производительность приложения и сохранить состояния, такие компоненты выносят отдельно в компонент Layout. Он является оберткой для всего нашего приложения или его части. В качестве пропcов он принимает children — ту часть страницы, которая будет изменяться, в отличие от хедера, футера и других компонентов.
В pages-роутинге при необходимости воспользоваться Layout нам приходилось писать его самим, так как в Next не было его при развертывании проекта. В pages router в папке pages у нас находились зарезервированные файлы _app и _document. Файл app был необходим для инициализации страниц приложения, а _document обновлял теги html body и рендерился на сервере. Теперь функционал обоих этих файлов перешел в новый файл layout, о котором сейчас и пойдет речь.
В app-роутинге у нас есть встроенный файл layout. RootLayout представляет собой компонент, принимающий сhildren в качестве пропсов и состоящий из body, куда и выводит children. Если же для какого-то роута нам нужен отдельный layout, то нам следует добавить в папку страницы уже новый файл layout, тем самым переопределив его.
Template
Вслед за layout хочется рассказать об очень схожей возможности оборачивать страницы, которая называется Template. Структура template (дословно «шаблон») очень сильно схожа с layout, но с одним существенным отличием: при переходе на новую страницу шаблон создает новый экземпляр для каждого дочернего элемента, соответственно, происходит сбрасывание состояний и эффектов.
Template можно создать так же, как и layout, просто добавив одноименный файл, остальная логика отличий не имеет. Шаблоны могут быть полезны в случае, если внутри приложения нужна изоляция, нет необходимости сохранять состояние или нужно, чтобы эффекты отрабатывали при каждой смене роута. Шаблоном можно воспользоваться, например, при работе со статистикой или отслеживании просмотров страницы.
Группы роутов
Для улучшения файловой структуры нашего проекта в Next была добавлена возможность группировать роуты. Группы роутов не влияют на пути в нашем приложении, но позволяют добавлять несколько корневых layout, а также упрощать разработку, изолируя одну часть проекта от другой.
Допустим, возникает необходимость изолировать покупку, авторизацию или любую другую логику от всего остального приложения. В таком случае мы можем сделать группировку роутов. Для этого нужно создать папку с именем, обернув его в круглые скобки, после чего перенести наши страницы в нужную папку. Там мы можем задать для группы наших роутов свои отдельные layout и template файлы, которые не будут пересекаться с другими страницами и группами роутов.
Параллельные роуты
Для оптимизации нашего приложения, Next также добавил возможность параллельного роутинга в приложении. Мы можем указать кусочки нашей странички, которые могут грузиться параллельно.
Параллельная маршрутизация позволяет одновременно или выборочно отображать одну или несколько страниц в одном макете (Layout).
При этом у нас есть возможность отрисовки резервного варианта с помощью файла default. Во время «жесткой» навигации Next не может определить активное состояние слотов, поэтому отобразит содержание файла default или 404, если файла default нет.
В app router также была добавлена возможность работы с перехватывающими роутами. Их суть заключается в том, чтобы предоставлять альтернативное поведение приложения при смене роутинга с определенной страницы или группы роутов.
Самый простой и распространенный вариант — это работа с каталогом фото или других постов, при клике на который у нас должно открываться модальное окно на странице каталога. Но при перезагрузке мы должны попадать на отдельную страницу с фото. Роут при этом изменяться не должен.
При переходе со страницы /feed на /photo/id роутер будет перехватываться, url меняться, но мы будем оставаться на той же странице. При перезагрузке страницы /photo/id, роут перехватываться не будет, и мы будем попадать на обычную страницу.
Количество точек перед названием роута, который нужно будет перезаписать, имеет свое значение:
• (.) - для сегментов на одном уровне (см. пример выше)
• (..) - для сегментов на уровне выше (см. картинку. Изначально страница photo находится на одном уровне с feed, поэтому страница photo внутри папки feed имеет две точки)
• (..)(..) - для сегментов на два уровня выше
• (…) - для сегментов из корня app директории
Loading, Error, Not Found
В Next были добавлена возможность обработки загрузки и ошибки, соответственно loading и error файлов.
Компонент Loading упрощает нам работу при взаимодействии с асинхронным кодом. Он автоматически оборачивает страницу в React Suspense. Компонент Loading будет показан сразу же при первой загрузке. Важно отметить, что он будет работать только для серверных компонентов, но не для клиентских (о них мы поговорим позже).
Компонент Error позволяет нам обрабатывать ошибки на странице нашего приложения. Error автоматически оборачивает страницу и layout (если он есть) в React error boundary. Как и layout, error может быть у каждой страницы или группы роутов свой. Error принимает несколько аргументов: саму ошибку error и второй аргумент, функцию reset, которая должна заново отрисовать наш компонент с ошибкой. Как и в случае с Loading, компонент Error будет отрабатывать только при ошибке в серверном компоненте, но сам компонент будет являться клиентским, поэтому нужно будет добавить ‘use client’.
Работа с SSR, SSG, ISR и серверными компонентами
Посмотрим на то, как реализован SSR (Server side rendering), SSG (Static site generation) и ISR (Incremental Static Regeneration) в app router и pages router.
Для работы с SSR в pages router раньше мы экспортировали асинхронную функцию getServerSideProps. Объект props, который мы возвращаем из этой функции, будет передан в качестве пропсов в наш компонент на сервере. Функция будет исполняться только на сервере, а перерендер будет происходить для каждого запроса. Для работы со статической генерацией (SSG) pages-router предлагает нам воспользоваться getStaticProps или getStaticPaths (для определения динамических роутов). И, наконец, для инкрементальной статической регенерации (ISR, где мы кэшируем данные, но ревалидируем их через определенные интервалы времени), мы пользовались тем же getStaticProps с дополнительным параметром revalidate.
В отличие от pages router, где у нас есть getServerSideProps, в app router есть серверные компоненты, при этом по умолчанию используется статическая генерация (SSG). То есть если мы создадим даже самый простой компонент с выводом текста в теге <div> и захотим вывести в логе какое-нибудь число, то в консоле браузера при загрузке страницы будет пусто, потому что все это будет происходить на стороне сервера.
У серверных компонентов нет состояний, мы не можем использовать тот же useState.
Для получения данных мы создаем асинхронную функцию с использованием fetch. Важно понимать, что это не дефолтный fetch, а fetch от Next (пропатченный). В качестве аргументов он принимает url и дополнительные параметры (method, body и тд). В нем можно так же задать кэширование данных, у которого будет несколько вариантов значений. В зависимости от значения, мы можем выбрать, как будет генерироваться наша страница.
Значение cache: ‘no-store’ эквивалентно рендерингу с помощью SSR, значение cache ‘force-cache’ будет эквивалентно статической генерации (SSG).
Если же мы зададим значение с параметром revalidate, то это будет эквивалентно ISR — статической генерации с опцией ревалидации, где в качестве значения мы указывали количество секунд, через которое должна произойти ревалидация.
При этом наши страницы и компоненты могут быть асинхронными. Мы можем прописать async, и уже в самом компоненте сделать обычный запрос, например через библиотеку axios. И если раньше для этого нам нужно было бы пользоваться useEffect, писать асинхронную функцию, сохранять данные в стейт, то сейчас мы можем просто сделать запрос.
Как было написано выше, в случае длительного ожидания ответа, на клиенте автоматически будет показан компонент из loading файла.
Если при работе с запросами, вы не хотите использовать fetch, а предпочитаете воспользоваться сторонней библиотекой, сохраняя при этом возможность выбора генерации страницы с помощью SSR, ISR или SSG, то для этого в файле можно экспортировать зарезервированную константу revalidate или dynamic.
Клиентские компоненты
Добавить в наш компонент интерактив или какое-то состояние, работать с браузерными API в pages router мы могли по умолчанию. В app router мы должны воспользоваться специальными клиентскими компонентами (как уже было сказано выше, по умолчанию все компоненты у нас серверные). Обозначить их можно с помощью ‘use client’. К клиентским компонентам будут относиться кнопки, инпуты, компоненты, где нужно будет добавить анимацию, инфинити скролл и прочее.
Заключение
На этом статья подошла к концу, в ней мы описали лишь некоторую часть тех изменений, которые произошли в обновленном app роутере в Next.js. Как мы видим, часть функционала поменялась, были изменены подходы в работе с SSR, роутами, компонентами и прочим, добавлены новые возможности и исключены или перенесены некоторые инструменты предыдущих версий.
Стоит отметить, что app router все еще находится на стадии разработки, хотя на сайте Next уже написано о рекомендации создавать новые приложения именно с помощью app router и постепенно внедрять его в текущие проекты. Нас ждет еще не несколько нововведений и последующих доработок. Тем не менее, Next сохраняет свою концепцию, позволяя удобно создавать приложения с поддержкой SSR, SSG, ISR.
Благодарим за внимание!