Вопросы про 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: клиентский компонент получит готовый результат.
Частые ошибки кандидатов
- Называть RSC «новым SSR». Это разные механизмы, которые решают разные задачи.
- Ставить use client в корневой layout «чтобы всё работало». Тогда всё поддерево попадает в бандл и выгода теряется.
- Читать в серверном компоненте window или localStorage. Там нет браузерных API.
- Передавать секреты через props клиентскому компоненту. Всё, что пересекло границу, видно в браузере.
- Путать гидрацию с повторным рендером: гидрация связывает готовый 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.