Как устроен Next.js: разбираем ключевые особенности фреймворка на примере небольшого pet-проекта

На майской конференции React Conf 2024 команда React не только презентовала версию библиотеки React 19, но и рекомендовала использовать для старта новых JavaScript-проектов один из четырех фреймворков: Next.js, Remix, RedwoodJs или Expo. Позже аналогичная рекомендация появилась в официальной документации по React. Из этих фреймворков Next.js может похвастаться самым большим комьюнити, документацией и удобством. Если вы до сих пор его не освоили, то мы идем к вам!

Привет, Хабр! В этой статье расскажем, что такое Next.js, и покажем вам на примере небольшого pet-проекта его базовые возможности.

Определение

Next.js — это фреймворк на базе React для создания fullstack-приложений. Фреймворк хоть и молодой (2016), но развивается стремительно. Next.js активно применяют и поддерживают крупные компании вроде Google, Netflix, Uber и TikTok, а его создатель Vercel предлагает удобную инфраструктуру для деплоя приложений. Фреймворк решает многие типичные задачи, с которыми сталкиваются разработчики при создании приложений на React, и позволяет сосредоточиться на бизнес-логике.

Ключевые особенности Next.js

0c9468b6038ac5d0d2b8bec9c16063e0.png

  1. Маршрутизация на основе файловой системы.

  2. Клиентский (CSR) и серверный рендеринг (SSR, SSG, ISR) с использованием клиентских и серверных компонентов.

  3. Автоматическая оптимизация шрифтов и изображений.

  4. Расширенный fetch c поддержкой кэширования.

  5. Эффективная SEO-оптимизация.

  6. Поддержка предпочитаемых методов стилизации, включая модули CSS, Tailwind CSS и CSS-in-JS.

  7. Собственный сборщик Turbopack, написанный на языке Rust специально под Next.js.

Некоторые из этих пунктов разберем подробнее на примере простенького pet-проекта из четырех страниц:

c39a5a5c4a4de69e2ece264237128d8b.png

Старт

Чтобы начать работать с Next.js, откройте в терминале папку, в которой будет находиться ваш проект, и запустите команду npx create-next-app@latest — она установит последнюю версию фреймворка и инициализирует новый проект. Подробнее об установке можно прочитать здесь.

Работу можно вести в любой привычной вам IDE. Наш pet-проект был реализован в интегрированной среде разработки WebStorm.

Маршрутизация

Начиная с 13-й версии, когда был представлен новый App Router, вся маршрутизация в Next.js строится на основе файловой системы каталога app. До 13-й версии использовался каталог pages.

Работать с папкой app легко: нужно просто добавить файл, и маршрут построится автоматически. Вложенные папки внутри app определяют навигацию, следуя от корневой папки до финальной.

Также Next.js предоставляет набор специальных файлов для создания пользовательского интерфейса с определенным поведением.

Некоторые из них:

  • page — пользовательский интерфейс маршрута;

  • layout — общий пользовательский интерфейс для сегмента и его дочерних элементов;

  • loading — загрузка пользовательского интерфейса для сегмента и его дочерних элементов;

  • error — пользовательский интерфейс ошибок для сегмента и его дочерних элементов;

  • middleware — промежуточное ПО, которое позволяет выполнять определенные действия до того, как пользователь увидит страницу.

В нашем примере: корневой каталог app с подкаталогами about, gallery, posts. Когда в этих подкаталогах есть файл с названием page, мы можем увидеть публичный маршрут, перейдя по этому адресу:

656641e74a03374857d0c63e810af680.png

А вот как выглядит корневой файл layout, который оборачивает все приложение, начиная с уровня app, и без которого работа приложения невозможна:

887b9727509d12aa25a33a5d5ed62aad.png

Отметим, что в Next.js также можно подключить дополнительные файлы layout, создав их на уровне любого маршрута. Мы сделали один на уровне about, чтобы поменять на этой странице цвет фона и шрифты:

e068d267c6f72fb16d61d02902bbe9bd.png

Результат:

a7eb7f4cffaf556f879a4053270b6e4b.png

Шрифты, изображения и ссылки

Next автоматически оптимизирует все шрифты, включая пользовательские. Мы можем использовать любой локальный шрифт или шрифт Google Fonts без отправок запросов в Google.

Чтобы подключить шрифт на уровне корневого или дополнительного layout, достаточно импортировать его из Next.js, задать необходимые параметры, такие как толщина и степень наклонности, и передать шрифт как className.

d289d4ba80a2d27cdf4d4d455fe72c8d.png

Компонент Image в Next.js расширяет стандартный тег . Его плюсы:

  • оптимизация размера через параметр sizes, при использовании которого Next.js формирует набор картинок под разные экраны и затем автоматически выбирает нужный;

  • предотвращение смещения макета при загрузке изображений;

  • ускоренная загрузка страниц за счет того, что изображения загружаются, только когда они попадают в область просмотра;

  • возможность настройки форматов изображений.

Если картинка подгружается с внешнего источника, то обязательно передать ее ширину и высоту:

70535638eacc818108348ebc51b5ff15.png

А также прописать в nextConfig параметры protocol и hostname:

4b5088624ef1aaea8ffb906ba6544680.png

Компонент Link Next.js расширяет стандартный тег , обеспечивая навигацию между страницами на стороне клиента. Является рекомендуемым и основным способом навигации в Next.js.

Метаданные

Есть два способа добавить метаданные в приложение:

1. Для добавления статических метаданных необходимо из файлов layout или page экспортировать объект с типом Metadata с необходимыми параметрами.

Для примера мы задали параметры title и description:

aac200f6db642658c290018edc78e3a1.png

2. Для добавления динамических метаданных необходимо экспортировать не объект, а функцию generateMetadata с необходимыми параметрами. Такая возможность поддерживается только в серверных компонентах. Чтобы ее реализовать, нужно создать новый сегмент и поместить название в квадратные скобки, после чего на уровне уже самой страницы получить это название как параметр объекта пропса params:

3594750f88a0ff03886c9c8fed2169b2.png

В нашем примере таким образом созданы страницы отдельных постов, на которые можно попасть из общего списка постов, и заданы title:

72833007953771c053a23d52a0149c1c.png

Метаданные можно добавлять с уровней page или layout.

Серверные компоненты

Рендерятся на стороне сервера. По умолчанию все компоненты в Next.js являются серверными.

Что это дает:

  • более быстрая загрузка страниц;

  • безопасность: токены, ключи, конфиденциальные данные хранятся на сервере;

  • при рендеринге на сервере результат можно кэшировать и повторно использовать при последующих запросах и между пользователями, тем самым улучшая производительность;

  • потоковая передача, или стриминг, который позволяет разделить работу рендеринга на части и передавать их клиенту по мере готовности.

В серверных компонентах невозможно работать со стейтом, методами жизненного цикла и событиями: поскольку серверные компоненты рендерятся на сервере, мы не можем использовать React Hooks, Context или API браузера, которые доступны в клиентских компонентах.

Серверные компоненты (React Server Components, RSC) рендерятся в два этапа — на стороне сервера и на стороне клиента.

На сервере:

  1. React рендерит серверные компоненты в специальный формат данных, который называется RSC Payload.

  2. Next.js использует RSC Payload и инструкции JavaScript для клиентских компонентов, чтобы рендерить HTML на сервере.

Затем на клиенте:

  1. HTML используется для моментального показа быстрого интерактивного предпросмотра — это только для первоначальной загрузки страницы.

  2. RSC Payload используется для согласования деревьев клиентского и серверного компонентов и обновления пользовательского интерфейса (Document Object Model, DOM).

  3. Инструкции JavaScript используются для гидратации клиентских компонентов и обеспечения интерактивности приложения.

Серверные компоненты также необходимо дебажить. Для этого нужно указать в сборщике флаг inspect:

15506b8d86f6823a2bc4aaf315e52b50.png

Клиентские компоненты

Привычные пользователям React компоненты для написания интерактивного пользовательского интерфейса.

Для того чтобы использовать клиентские компоненты в Next.js, необходимо в самом начале файла объявить директиву use client.

Чтобы оптимизировать начальную загрузку страницы, Next.js будет использовать API- интерфейсы React для рендеринга статического предварительного просмотра HTML на сервере как для клиентских, так и для серверных компонентов. Это означает, что, когда пользователь впервые посещает приложение, он сразу же видит содержимое страницы, не дожидаясь, пока клиент загрузит, проанализирует и выполнит пакет клиентского компонента JavaScript.

Обработчики маршрутов

Также в Next.js мы можем создавать API-эндпоинты. Для этого внутри каталога app нужно определить файл с названием route. Единственное правило — файлы route и page не должны лежать на одном уровне во избежание конфликтов.

Как может выглядеть путь: app/api/gallery/route.ts

21865e4efc0653189012273b61cff6a6.png

Внутри файла route мы должны экспортировать функцию с названием одного из HTTP-методов (в нашем примере — GET). Если вызывается неподдерживаемый метод, Next.js вернет ошибку 405.

Пример функции:

export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...')
  const data = await res.json()

  return Response.json(data)
}

В нашем pet-проекте эндпоинты использованы на странице Галереи, благодаря чему пользователь может добавить и увидеть картинку. В коде это реализовано через fetch-запрос:

11bdcfdf2277b5b190cc9a0652d592b6.png7835dba6d2355c4cecb166fdec4918b3.png

Здесь же упомяну, что речь идет не про обычный fetch, а про расширенный fetch Next.js, который обогащен дополнительными возможностями. На скриншоте выше можно увидеть настройку кэширования с флагом no-store, чтобы данные каждый раз рендерились по новой.

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

b499cc38a78adc11a9b0de9477ee2930.png

Впрочем, ревалидацию можно настроить не только на уровне fetch, но и на уровне страницы — через экспорт переменной revalidate.

Server actions

Асинхронные функции, которые выполняются на сервере, стали доступны в Next.js с 14-й версии. Их можно вызвать с помощью атрибута action тега

, в результате чего в action автоматически попадает объект с типом FormData, либо через обработчики событий, useEffect и другие элементы формы (button).

Для обработки состояния полей формы не нужно использовать useState — вместо этого можно извлекать данные напрямую, используя FormData-методы для их дальнейшей отправки, например, сразу в базу данных.

Серверные компоненты по умолчанию поддерживают прогрессивное улучшение, то есть форма будет отправлена, даже если JavaScript еще не загружен или отключен.

После гидратации браузер не обновляется при отправке формы.

В нашем примере серверное действие — это добавление фотографий:

e3e3a6477ef0530527f88778a2433230.png

Обратите внимание на встроенный метод Next.js для ревалидации пути — revalidatePath, который позволяет моментально увидеть результат при добавлении новой картинки:

2ae65ab1bb9a933b3be77057d09cdee1.png

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

Выводы

Отличительные особенности Next.js — легкая оптимизация производительности и простая интеграция с современными технологиями — делают его отличным вариантом для разработчиков.

Он идеально подойдет для:

  • проектов, где важна SEO-оптимизация, — блоги, интернет-магазины или корпоративные сайты;

  • проектов, где важна высокая производительность и минимальное время загрузки;

  • многостраничных приложений, которые нуждаются в динамическом рендеринге данных;

  • проектов с необходимостью в API, когда требуется интеграция с сервером и внешними сервисами.

Резюмируем: Next.js — мощный и гибкий фреймворк, который сильно упрощает создание React-приложений.

© Habrahabr.ru