Собеседования

React Server Components на собеседовании: границы, данные и гидрация

Чем серверные компоненты отличаются от SSR, что делает use client, какие props проходят через границу и где ошибаются кандидаты. Примеры на Next.js.

Вопросы про React Server Components (RSC) появились на собеседованиях вместе с распространением Next.js App Router. Интервьюер обычно проверяет три вещи: понимаете ли вы, где выполняется код, как данные пересекают границу сервер - клиент, и почему это влияет на размер бандла и скорость. Разберём ответы на типичные вопросы и код, который полезно уметь написать.

Что такое серверный компонент

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

Главное отличие от SSR: SSR - способ превратить дерево в HTML для первого ответа, после чего весь код компонентов всё равно загружается и гидрируется в браузере. В модели RSC гидрируются только клиентские компоненты. Серверная часть приходит как результат рендера и не требует своего JavaScript.

Пример: каталог с кнопкой «В корзину»

Страница получает данные на сервере и рендерит список. Интерактивна только кнопка, поэтому только она становится клиентским компонентом. Пример рассчитан на Next.js App Router: параметр cache у fetch - расширение фреймворка.

TSX// app/products/page.tsx — серверный компонент: async и доступ к данным import { AddToCart } from './add-to-cart' type Product = { id: string; title: string; price: number } async function getProducts(): Promise<Product[]> { const response = await fetch('https://api.example.com/products', { cache: 'no-store' }) if (!response.ok) throw new Error('Каталог недоступен') return response.json() } export default async function ProductsPage() { const products = await getProducts() return ( <ul> {products.map((product) => ( <li key={product.id}> {product.title}: {product.price} ₽ <AddToCart productId={product.id} /> </li> ))} </ul> ) }

TSX// app/products/add-to-cart.tsx — клиентский компонент: состояние и обработчик 'use client' import { useState } from 'react' export function AddToCart({ productId }: { productId: string }) { const [added, setAdded] = useState(false) return ( <button type="button" aria-pressed={added} onClick={() => setAdded(true)} data-product={productId}> {added ? 'В корзине' : 'В корзину'} </button> ) }

Комментарий перед директивой допустим: use client должна идти раньше импортов и другого кода. Обратите внимание, что в клиентский компонент передан только productId - строка. Объект product тоже можно передать, если он сериализуем, но передавать лишние данные через границу - значит увеличивать полезную нагрузку страницы.

Что можно передать через границу

  • Можно: строки, числа, булевы значения, null, массивы и простые объекты из них, Date, Map, Set, промисы, JSX.
  • Нельзя: обычные функции и классы с методами. Обработчик onClick не может прийти с сервера.
  • Исключение - Server Functions (директива use server): клиент получает ссылку и вызывает функцию на сервере.

Типичный вопрос с подвохом: «Как вложить серверный компонент в клиентский?» Импортировать его внутрь модуля с use client нельзя - он станет клиентским. Но можно передать уже отрендеренный серверный компонент как children или другой prop: клиентский компонент получит готовый результат.

Частые ошибки кандидатов

  1. Называть RSC «новым SSR». Это разные механизмы, которые решают разные задачи.
  2. Ставить use client в корневой layout «чтобы всё работало». Тогда всё поддерево попадает в бандл и выгода теряется.
  3. Читать в серверном компоненте window или localStorage. Там нет браузерных API.
  4. Передавать секреты через props клиентскому компоненту. Всё, что пересекло границу, видно в браузере.
  5. Путать гидрацию с повторным рендером: гидрация связывает готовый HTML с обработчиками, а не рисует страницу заново.

Как ответить про производительность

Хороший ответ связывает архитектуру с метриками. Меньше клиентского JavaScript - меньше работы основного потока при загрузке, что помогает отзывчивости (INP). Данные, полученные на сервере, приходят вместе с разметкой, без каскада запросов из браузера - это помогает LCP. Но серверный рендер на каждый запрос нагружает сервер, а долгий запрос к данным задерживает ответ: для этого используют Suspense и потоковую отдачу. Методику измерений смотрите в разборе Core Web Vitals для React.

Что потренировать до собеседования

Возьмите страницу из своего пет-проекта и разметьте компоненты: какие требуют состояния и событий, а какие только показывают данные. Опустите границу use client как можно ниже и сравните размер бандла до и после. Вопросы по хукам, рендерингу и состоянию собраны в статье вопросы на собеседовании по React, а вся программа - на странице менторства Frontend.

Источники

Справочник React: Server Components, директива use client, Server Functions. Реализация во фреймворке: Next.js - Server and Client Components.

Вопросы по теме

Server Components заменяют SSR?
Нет. SSR превращает дерево компонентов в HTML для первого ответа. Server Components определяют, какой код выполняется только на сервере и не попадает в бандл. Во фреймворках вроде Next.js App Router они работают вместе.
Нужно ли ставить use client в каждый интерактивный компонент?
Директива нужна в модуле, который становится точкой входа в клиентскую часть. Компоненты, импортированные из него, уже клиентские. Чем ниже по дереву граница, тем меньше JavaScript получает браузер.
Можно ли использовать useEffect в серверном компоненте?
Нет. Серверный компонент не живёт в браузере, у него нет состояния и эффектов. Данные он получает напрямую, например через async/await, а интерактивность передаётся клиентским компонентам.

Читайте также

Все статьи

Обсудим вашу подготовку?

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

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

Что не получается, что уже пробовали. От 100 знаков, сейчас 0.
Где работали или учились, сколько лет, какой стек, сколько откликов и собеседований. От 100 знаков, сейчас 0.
PDF или DOCX, до 5 МБ. Файл видит только администратор.
GitHub, hh, LinkedIn, сайт. Можно несколько, через пробел или с новой строки.