Как устроен 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
Маршрутизация на основе файловой системы.
Клиентский (CSR) и серверный рендеринг (SSR, SSG, ISR) с использованием клиентских и серверных компонентов.
Автоматическая оптимизация шрифтов и изображений.
Расширенный fetch c поддержкой кэширования.
Эффективная SEO-оптимизация.
Поддержка предпочитаемых методов стилизации, включая модули CSS, Tailwind CSS и CSS-in-JS.
Собственный сборщик Turbopack, написанный на языке Rust специально под Next.js.
Некоторые из этих пунктов разберем подробнее на примере простенького pet-проекта из четырех страниц:
Старт
Чтобы начать работать с 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, мы можем увидеть публичный маршрут, перейдя по этому адресу:
А вот как выглядит корневой файл layout, который оборачивает все приложение, начиная с уровня app, и без которого работа приложения невозможна:
Отметим, что в Next.js также можно подключить дополнительные файлы layout, создав их на уровне любого маршрута. Мы сделали один на уровне about, чтобы поменять на этой странице цвет фона и шрифты:
Результат:
Шрифты, изображения и ссылки
Next автоматически оптимизирует все шрифты, включая пользовательские. Мы можем использовать любой локальный шрифт или шрифт Google Fonts без отправок запросов в Google.
Чтобы подключить шрифт на уровне корневого или дополнительного layout, достаточно импортировать его из Next.js, задать необходимые параметры, такие как толщина и степень наклонности, и передать шрифт как className.
Компонент Image в Next.js расширяет стандартный тег . Его плюсы:
оптимизация размера через параметр sizes, при использовании которого Next.js формирует набор картинок под разные экраны и затем автоматически выбирает нужный;
предотвращение смещения макета при загрузке изображений;
ускоренная загрузка страниц за счет того, что изображения загружаются, только когда они попадают в область просмотра;
возможность настройки форматов изображений.
Если картинка подгружается с внешнего источника, то обязательно передать ее ширину и высоту:
А также прописать в nextConfig параметры protocol и hostname:
Компонент Link Next.js расширяет стандартный тег , обеспечивая навигацию между страницами на стороне клиента. Является рекомендуемым и основным способом навигации в Next.js.
Метаданные
Есть два способа добавить метаданные в приложение:
1. Для добавления статических метаданных необходимо из файлов layout или page экспортировать объект с типом Metadata с необходимыми параметрами.
Для примера мы задали параметры title и description:
2. Для добавления динамических метаданных необходимо экспортировать не объект, а функцию generateMetadata с необходимыми параметрами. Такая возможность поддерживается только в серверных компонентах. Чтобы ее реализовать, нужно создать новый сегмент и поместить название в квадратные скобки, после чего на уровне уже самой страницы получить это название как параметр объекта пропса params:
В нашем примере таким образом созданы страницы отдельных постов, на которые можно попасть из общего списка постов, и заданы title:
Метаданные можно добавлять с уровней page или layout.
Серверные компоненты
Рендерятся на стороне сервера. По умолчанию все компоненты в Next.js являются серверными.
Что это дает:
более быстрая загрузка страниц;
безопасность: токены, ключи, конфиденциальные данные хранятся на сервере;
при рендеринге на сервере результат можно кэшировать и повторно использовать при последующих запросах и между пользователями, тем самым улучшая производительность;
потоковая передача, или стриминг, который позволяет разделить работу рендеринга на части и передавать их клиенту по мере готовности.
В серверных компонентах невозможно работать со стейтом, методами жизненного цикла и событиями: поскольку серверные компоненты рендерятся на сервере, мы не можем использовать React Hooks, Context или API браузера, которые доступны в клиентских компонентах.
Серверные компоненты (React Server Components, RSC) рендерятся в два этапа — на стороне сервера и на стороне клиента.
На сервере:
React рендерит серверные компоненты в специальный формат данных, который называется RSC Payload.
Next.js использует RSC Payload и инструкции JavaScript для клиентских компонентов, чтобы рендерить HTML на сервере.
Затем на клиенте:
HTML используется для моментального показа быстрого интерактивного предпросмотра — это только для первоначальной загрузки страницы.
RSC Payload используется для согласования деревьев клиентского и серверного компонентов и обновления пользовательского интерфейса (Document Object Model, DOM).
Инструкции JavaScript используются для гидратации клиентских компонентов и обеспечения интерактивности приложения.
Серверные компоненты также необходимо дебажить. Для этого нужно указать в сборщике флаг inspect:
Клиентские компоненты
Привычные пользователям React компоненты для написания интерактивного пользовательского интерфейса.
Для того чтобы использовать клиентские компоненты в Next.js, необходимо в самом начале файла объявить директиву use client.
Чтобы оптимизировать начальную загрузку страницы, Next.js будет использовать API- интерфейсы React для рендеринга статического предварительного просмотра HTML на сервере как для клиентских, так и для серверных компонентов. Это означает, что, когда пользователь впервые посещает приложение, он сразу же видит содержимое страницы, не дожидаясь, пока клиент загрузит, проанализирует и выполнит пакет клиентского компонента JavaScript.
Обработчики маршрутов
Также в Next.js мы можем создавать API-эндпоинты. Для этого внутри каталога app нужно определить файл с названием route. Единственное правило — файлы route и page не должны лежать на одном уровне во избежание конфликтов.
Как может выглядеть путь: app/api/gallery/route.ts
Внутри файла 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-запрос:
Здесь же упомяну, что речь идет не про обычный fetch, а про расширенный fetch Next.js, который обогащен дополнительными возможностями. На скриншоте выше можно увидеть настройку кэширования с флагом no-store, чтобы данные каждый раз рендерились по новой.
Настройки могут быть разными. Например, можно сделать ревалидацию, указав таймер для выполнения повторного запроса на сервер:
Впрочем, ревалидацию можно настроить не только на уровне fetch, но и на уровне страницы — через экспорт переменной revalidate.
Server actions
Асинхронные функции, которые выполняются на сервере, стали доступны в Next.js с 14-й версии. Их можно вызвать с помощью атрибута action тега