Что должен знать каждый разработчик JavaScript о Next.js 13

Как я писал в своей последней статье, Next.js 13 App Router наконец-то стал стабильным с версии 13.4, а это значит, что пришло время рассмотреть возможность его использования в вашем следующем проекте.

В этой статье мы углубимся в Next.js 13 и рассмотрим несколько вещей, которые вы должны знать, прежде чем использовать его для своего следующего веб-приложения. Пристегнитесь, потому что мы собираемся совершить захватывающее путешествие в будущее веб-разработки.

Компоненты сервера React (RSC)

Новый Next.js 13 App Router был создан в тесном сотрудничестве с React 18. Одной из основных новых функций React 18 являются React Server Components (RSC). Чтобы начать работать в Next.js 13, вам нужно осознать эту новую парадигму.

В прошлом React был в основном библиотекой рендеринга пользовательского интерфейса на стороне клиента. С добавлением RSC цель состоит в том, чтобы отрисовать как можно больше вашего приложения на сервере во время сборки (мы рассмотрим различные режимы рендеринга ниже).

Когда маршрут загружается с помощью Next.js, исходный HTML отображается на сервере. Затем этот HTML-код последовательно улучшается в браузере, позволяя клиенту взять на себя управление приложением и добавить интерактивности путем асинхронной загрузки среды выполнения Next.js и React на стороне клиента.

- Из раздела React Essentials на сайте документации Next.js.

В React 18 были добавлены две новые директивы «использовать клиент» и «использовать сервер», чтобы контролировать, где компоненты отображаются на уровне файла. Эти новые директивы используются в Next.js 13 для управления включением кода в пакет на стороне клиента или нет.

С помощью директивы «использовать сервер» мы можем указать, что компонент не обязательно должен быть включен в пакет, который загружает клиент. Компонент будет отображаться на сервере во время сборки или выполнения.

Как вы можете видеть в приведенном ниже коде, мы также можем использовать async await в серверных компонентах, что делает их отличными для загрузки данных.

// ServerComponent.ts
"use server"

export default async function ServerComponent() {
  const data = await fetchData()

  return //...
}

Если компоненту необходимо использовать React Hooks, например useEffect, или ему нужен доступ к API браузера, мы можем использовать директиву «use client», чтобы указать, что компонент должен быть включен в пакет клиента.

// ClientComponent.tsx
// use client is needed here since we are using React hooks and accessing
// the localStorage browser API
"use client"
export default function ClientComponent() {
  const [todos, setTodos] = useState([])
  useEffect(() => {
    const cachedTodos = localStorage.get('todos')
    setTodos(todos)
  })

  return (
    <>
      {todos.map(todo => <Todo {...todo} />)}
    </>
  )
}

Для получения дополнительной информации см. исходный RFC React, где были предложены эти директивы: https://github.com/reactjs/rfcs/pull/227.

Серверные компоненты используются по умолчанию в Next.js.

В мире Next.js 13 серверные компоненты теперь используются по умолчанию, а это означает, что большинству ваших компонентов не нужны директивы «использовать сервер» или «использовать клиент».

Единственный раз, когда вам нужно использовать эти директивы, это когда вы создаете граничный компонент.

// page.tsx
// React Server Component, will not be included in the client 
export default function Page() {

  return (
    <Provider>
      <TodoList />
    </Provider>
  )
}

// TodoList.tsx
// use client is needed here since we are using React hooks
"use client"
export default function TodoList() {
  useEffect(() => {})

  return (
    <>
      {todos.map(todo => <Todo {...todo} />)}
    </>
  )
}

// TodoList.tsx
// No "use client" needed here, even though we are using hooks
// because this component is only ever rendered within another client component
export default function Todo() {
  useEffect(() => {})

  return (
    <>
      {todos.map(todo => <Todo {...todo} />)}
    </>
  )
}

Клиентские компоненты тоже рендерятся на сервере!

Когда вы используете директиву «использовать клиент» для создания клиентского компонента, это не означает, что он отображается только на стороне клиента. На самом деле, большинство клиентских компонентов визуализируются на сервере при выполнении рендеринга на стороне сервера (SSR) или создания статического сайта (SSG).

Единственный случай, когда клиентский компонент не будет отображаться на сервере, — это когда вы прямо указываете, что этого делать не следует. Один из способов сделать это — использовать next/dynamic с опцией ssr: false (примечание: Vercel рекомендует использовать React.lazy и Suspense напрямую вместо next/dynamic):

import dynamic from 'next/dynamic';
 
const DynamicHeader = dynamic(() => import('../components/header'), {
  ssr: false,
});

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

Это означает, что вам нужно подумать о том, как клиентский компонент будет отображаться на сервере. Один из способов проверить это — отключить JavaScript в вашем браузере и посмотреть, как отображается страница. Вы должны увидеть, что страница отображается полностью, но интерактивные элементы отключены.

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

Составление клиентских и серверных компонентов

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

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

А вот быстрый пример h

Next.js 13 режимов рендеринга

В Next.js 13 представлены различные среды и режимы рендеринга, позволяющие вам выбирать оптимальную стратегию рендеринга для вашего приложения для каждого компонента.

Контент отображается в двух разных средах:

  • Сторона клиента — клиентские компоненты предварительно визуализируются и кэшируются на сервере. JSON генерируется для данных, используемых в клиентских компонентах, и передается в React во время Hydration.
  • Сторона сервера — контент отображается на сервере с помощью React, и генерируется статический HTML. React использует статический HTML для гидратации в браузере, не требуя дополнительного JavaScript на клиенте.

На сервере используются два разных режима рендеринга:

  • Статические — и клиентские, и серверные компоненты отображаются как статический HTML во время сборки. Статический контент может быть повторно проверен, что позволяет обновлять его постранично, чтобы поддерживать динамическое обновление данных в соответствии с их источниками. Статически сгенерированный контент легко кэшируется, что повышает производительность, удобство для пользователей и оптимизацию для поисковых систем.
  • Динамический — как клиентские, так и серверные компоненты отображаются на сервере при выполнении запроса. Содержимое не кэшируется.

Предыдущие версии Next.js использовали другую терминологию для этих понятий. Я включаю их ниже и показываю, как они связаны с новой терминологией Next.js 13.

  • Создание статического сайта (SSG): режим статического рендеринга.
  • Инкрементная статическая регенерация (ISR): режим статического рендеринга с повторной проверкой.
  • Рендеринг на стороне сервера (SSR): режим динамического рендеринга.
  • Визуализация на стороне клиента (CSR): клиентские компоненты

Обязательно ознакомьтесь с сайтом документации Next.js по теме.

Файлы ствола немного сломаны

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

.
└── utils/
    ├── api.ts
    ├── dom.ts
    ├── formatter.ts
    ├── parser.ts
    └── index.ts

Файл index.ts в этом случае может выглядеть примерно так:

export { default as parser } from './parser'
export { default as formatter } from './formatter'
export * from './api'

Это позволяет вам импортировать одну из ваших утилит, например:

import { parser } from 'utils'
// Rather than:
import parser from 'utils/parser'

Этот шаблон становится немного сложным с Next.js 13, если не соблюдать осторожность, потому что он может случайно передать серверный код клиенту. Next.js имеет полезную утилиту, которая позволяет вам добавить: import "server-only" в начало модуля, чтобы вам не удалось загрузить его в пакет клиента. Рекомендуется использовать это для ваших файлов стволов и для импорта утилит на стороне клиента по их прямым путям. Вы также можете добавить отдельный файл «только для клиента», чтобы гарантировать, что модули, предназначенные для клиента и сервера, не будут смешаны.

Интеграция библиотеки: работа в процессе

Сообщество открытого исходного кода добилось значительных успехов, но все еще есть некоторые библиотеки, которые не так хорошо интегрированы, как хотелось бы. Например, MUI, Emotion, Styled Components и Apollo GraphQL имеют небольшие проблемы или нарушения с маршрутизатором приложений Next.js 13.

Например, с Material UI (MUI) вы можете оказаться в затруднительном положении, когда вам придется прибегнуть к некоторой, скажем так, неортодоксальной логике или, в качестве альтернативы, назначить большинство ваших компонентов клиентскими компонентами. Для получения дополнительной информации по этой конкретной проблеме я настоятельно рекомендую посетить эти обсуждения на GitHub: issue #34896 и issue #34905.

Если вы работаете с Apollo, вы можете столкнуться с ситуациями, когда вам нужно получить одни и те же данные как на клиенте, так и на сервере. Решение этой проблемы заключается в гидратации кеша Аполлона, но для этого требуется немного усилий. К счастью, Apollo выпустила экспериментальный пакет для помощи в этом; узнать об этом подробнее можно здесь и прочитать их официальный анонс.

Группы маршрутов

Группы маршрутов — отличная функция, представленная в Next.js 13, особенно когда вашему приложению требуется несколько корневых макетов. Next.js 13.3 удалил устаревший специальный файл head.js и заменил его API generateMetadata. Одним из недостатков этого подхода является то, что было сложно добавлять скрипты и другие вещи в <head>, когда у вас были страницы с другим содержимым.

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

.
├── (navigation)/
│   ├── dashboard/
│   │   └── page.tsx
│   └── layout.tsx
└── (navless)/
    ├── auth/
    │   └── page.tsx
    └── layout.tsx

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

import { PropsWithChildren } from 'react'

export default async function Layout({ children }: PropsWithChildren) {
  const { head, header, footer } = await fetchNavigationMarkup()

  return (
    <html>
      <head>
        {head}
      </head>
      <body>
        {header}
        {children}
        {footer}
      </body>
    </html>
  )
}

Дополнительные ресурсы

Завершение

В этой статье мы рассмотрели семь главных вещей, о которых нужно знать, прежде чем приступить к работе с Next.js 13.

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

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

Отправляясь в мир Next.js 13, помните, что с большой силой приходит большая ответственность. Используйте эти функции с умом, и вы в кратчайшие сроки создадите действительно потрясающие веб-приложения. Оставайтесь любопытными, продолжайте исследовать и никогда не переставайте учиться. До следующего раза, удачного кодирования!