Разбираемся в серверных и клиентских компонентах в Next.js: когда, как и почему?

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

9c7980abeca612513bf0a7399e349f57.png

Текст и примеры в статье относятся к Next.js 13.4 и старше, в котором React Server Components обрели статус stable и стали рекомендуемыми при разработке приложений на Nextjs. 

Что такое серверный компонент (RSC), и как он рендерится

React Server Components рендерятся исключительно на сервере. Их код не включается в файл JavaScript бандла, поэтому они никогда не гидратируются или перерендериваются.

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

RSC рендерится в два этапа, на сервере:

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

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

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

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

  2. RSC payload используется для согласования деревьев клиентского и серверного компонентов и обновления DOM.

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

Что такое RSC payload?

RSC payload — это компактное бинарное представление отрендеренного дерева серверных компонентов React. RSC payload используется на клиенте для обновления DOM браузера и содержит:

  1. Отрендеренный результат серверных компонентов.

  2. Плейсхолдеры для того, где должны появиться отрендеренные клиентские компоненты, и ссылки на их JavaScript файлы-чанки.

  3. Любые пропсы, переданные из серверного компонента в клиентский компонент.

Преимущества RSC

  1. Улучшение производительности приложения т.к. на клиент не отправляются тяжелые зависимости, которые могли использоваться для рендера компонента на сервере (Markdown, code highlighter и т.п.)

  2. Улучшаются web vitals метрики приложения (TTI и т.п.)

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

Недостатки RSC

  1. RSC payload увеличивает размер HTML

  2. На клиент могут утечь секреты предназначенные только для сервера (токены, ключи и т.п.). Вопросы безопасности next.js приложений подробно описаны в статье https://nextjs.org/blog/security-nextjs-server-components-actions

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

Что такое клиентский компонент, и как он рендерится

Клиентские компоненты позволяют вам создавать интерактивный пользовательский интерфейс, который предварительно отрисовывается на сервере и может использовать клиентский JavaScript для выполнения в браузере.

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

Несмотря на их название, «клиентские компоненты» рендерятся на сервере и выполняются как на сервере, так и на клиенте.

1ceded92299f51e2493b9e9f53b9297f.png

Мы можем легко превратить серверный компонент в клиентский, добавив директиву «use client» в начало файла или переименовав его в counter.client.js:

'use client';
 
export default function Counter() {
  return 
Counter - client component
; }

Когда использовать серверный, a когда клиентский компонент?

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

Чтобы понять какой тип компонента подходит в том или ином случае, можно воспользоваться удобной таблицей таблицей размещенной на сайте документации next.js

dd79bb1a9a4edcf19fb3d9f7b71be7d6.png

В RSC мы не можем использовать React hooks, Context или API браузера. Однако мы можем использовать только API серверных компонентов, такие как headers, cookie и т. д.

Важно: Серверные компоненты могут импортировать клиентские компоненты.

Когда мы используем клиентские компоненты, мы можем использовать React hooks, Context и API, доступные только в браузере. Однако мы не можем использовать некоторые API, доступные только в серверных компонентах, такие как headers, cookie и т. д.

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

С появлением React Server Components общей рекомендацией стало перемещение клиентских компонентов в концевые узлы вашего дерева компонентов, где это возможно. Однако иногда требуется условное отображение серверных компонентов с использованием интерактивности на стороне клиента. 

Допустим, у нас есть такой клиентский компонент:

'use client'
 
import { useState } from 'react'
 
export default function ClientComponent({
  children,
}: {
  children: React.ReactNode
}) {
  const [show, setShow] = useState(false)
 
  return (
    <>
      
      {show && children}
    
  )
}

ClientComponent не знает, что его дочерние элементы в конечном итоге будут заполнены результатом рендера серверного компонента. Единственная обязанность ClientComponent — решить, куда в конечном итоге будут помещены дочерние элементы.

// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from './client-component'
import ServerComponent from './server-component'
 
// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    
      
    
  )
}

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

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

FAQ

Почему нужно использовать Next.js React Server Components (RSC)?

React Server Components (RSC) предоставляют новый способ создания приложений, который позволяет разработчикам разделять код между клиентом и сервером. Это становится особенно полезным для крупных проектов с большим объемом данных или динамическим контентом.

Как связаны RSC и Next.js? Могу ли я использовать RSC без Next.js?

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

Как это связано с Suspense?

API для получения данных Server Components интегрированы с Suspense. RSC использует Suspense для предоставления состояний загрузки и разблокировки частей потока, чтобы клиент мог показывать что-то до того, как будет завершен весь ответ.

Какие преимущества производительности дает использование RSC?

Server Components позволяют вам переместить большую часть получения данных на сервер, чтобы клиенту не приходилось делать много запросов. Это также избавляет от типичных для получения данных в useEffect network waterfalls на клиенте.

Server Components также позволяют добавлять неинтерактивные функции в ваше приложение без увеличения размера JS бандла. Перемещение функций с клиента на сервер уменьшает начальный размер кода и время разбора клиентского JS. Также уменьшение числа компонентов клиента улучшает время работы процессора клиента. Клиент может пропустить серверно-сгенерированные части дерева во время согласования, потому что он знает, что они не могли быть затронуты обновлениями состояния.

Обязательно ли нужно мне использовать RSC?

Если у вас уже есть клиентское приложение, вы можете рассматривать его как дерево клиентских компонентов. Если это вас устраивает, замечательно! Серверные компоненты расширяют React для поддержки других сценариев и не являются заменой клиентским компонентам.

Является ли это заменой SSR?

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

Вы можете объединить серверные компоненты и SSR, где серверные компоненты рендерятся первыми, а клиентские компоненты рендерятся в HTML для быстрого неинтерактивного отображения во время гидратации. Когда они комбинируются таким образом, вы все равно получаете быстрый запуск, но также существенно снижаете объем загружаемого на клиенте JS.

Могу ли я постепенно переходить на RSC, переписывая кодовую базу проекта?

Да, с выходом app router и RSC, предыдущий подход по-прежнему работает и можно постепенно переходить на RSC подход. Здесь нужно отметить, что RSC компоненты работают только в app router. Есть подробное руководство по переходу на app router.

Полезные ссылки

  1. Understanding React Server Components — Vercel

  2. Preventing sensitive data from being exposed to the client

© Habrahabr.ru