Back

Распространённые ошибки при работе с React Server Components

Распространённые ошибки при работе с React Server Components

Вы внедрили Next.js App Router. Server Components используются по умолчанию. Всё должно работать быстрее, проще и эффективнее.

Вместо этого вы отлаживаете несоответствия гидратации в 2 часа ночи, недоумеваете, почему вырос размер бандла, и сомневаетесь, правильно ли вы разместили директиву 'use client'.

React Server Components представляют собой фундаментальный сдвиг в работе React-приложений. Граница между сервером и клиентом — это больше не просто вопрос развёртывания, это архитектурное решение, которое вы принимаете с каждым компонентом. Неправильный подход приводит к неочевидным багам, снижению производительности и коду, который противоречит фреймворку вместо того, чтобы использовать его возможности.

В этой статье рассматриваются наиболее распространённые ошибки при работе с React Server Components, которые я встречал в production-кодовых базах, и способы их избежать.

Ключевые выводы

  • Server Components используются по умолчанию в Next.js App Router — размещайте 'use client' как можно ниже в дереве компонентов, чтобы минимизировать размер бандла.
  • Используйте пакет server-only, чтобы предотвратить случайное попадание чувствительного серверного кода в клиентский бандл.
  • Всегда преобразовывайте несериализуемые значения (такие как функции и экземпляры классов) перед передачей их из Server Components в Client Components.
  • Server Actions ('use server') — это RPC-подобные эндпоинты, а не Server Components — валидируйте все входные данные и никогда не доверяйте данным от клиента.
  • Явно указывайте поведение кеширования с помощью revalidate или cache: 'no-store', поскольку значения по умолчанию в Next.js менялись в разных версиях.

Понимание различий между Server и Client Components

Прежде чем углубляться в подводные камни, давайте определим базовые понятия. В App Router компоненты по умолчанию являются Server Components. Они выполняются на сервере, не имеют доступа к браузерным API и не отправляют JavaScript на клиент.

Client Components требуют директивы 'use client'. Они могут использовать хуки вроде useState и useEffect, обращаться к браузерным API и обрабатывать пользовательские взаимодействия.

Граница между ними — это место, где происходит большинство ошибок.

Чрезмерное использование директивы ‘use client’

Наиболее частая ошибка при работе с RSC в Next.js App Router — слишком раннее обращение к 'use client'. Компоненту нужен useState? Помечаем его как клиентский компонент. Нужен обработчик onClick? Клиентский компонент.

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

// ❌ Вся страница становится клиентским компонентом
'use client'

import { useState } from 'react'

export default function ProductPage({ product }) {
  const [quantity, setQuantity] = useState(1)
  
  return (
    <div>
      <ProductDetails product={product} />
      <ProductReviews productId={product.id} />
      <QuantitySelector value={quantity} onChange={setQuantity} />
    </div>
  )
}
// ✅ Только интерактивная часть является клиентским компонентом
import ProductDetails from './ProductDetails'
import ProductReviews from './ProductReviews'
import QuantitySelector from './QuantitySelector'

export default function ProductPage({ product }) {
  return (
    <div>
      <ProductDetails product={product} />
      <ProductReviews productId={product.id} />
      <QuantitySelector /> {/* Это единственный клиентский компонент */}
    </div>
  )
}

Размещайте 'use client' как можно ниже в дереве компонентов. Изолируйте интерактивность в минимальные компоненты, которым она необходима.

Импорт серверного кода в Client Components

Когда клиентский компонент импортирует модуль, весь этот модуль (и его зависимости) отправляется в браузер. Импортировали клиент базы данных или файл, читающий секретные переменные окружения? Вы только что открыли серверный код клиентскому графу зависимостей.

// lib/db.js
import 'server-only' // Добавьте это, чтобы предотвратить случайный импорт на клиенте

export async function getUsers() {
  return db.query('SELECT * FROM users')
}

Пакет server-only (предоставляемый Next.js) вызывает ошибку сборки, если модуль когда-либо импортируется в клиентский компонент. Используйте его для любого кода, который никогда не должен попасть в браузер.

Передача несериализуемых значений через границу

Server Components передают пропсы в Client Components через сериализацию. Функции, экземпляры классов, Map и Set не могут пересечь эту границу.

// ❌ Экземпляры классов не сериализуются
export default async function UserProfile({ userId }) {
  const user = await getUser(userId)
  return <ClientProfile user={user} /> // user — это экземпляр класса
}

// ✅ Преобразуйте в простой объект
export default async function UserProfile({ userId }) {
  const user = await getUser(userId)
  return (
    <ClientProfile 
      user={{
        id: user.id,
        name: user.name,
        createdAt: user.createdAt.toISOString()
      }} 
    />
  )
}

Неправильное понимание React Server Actions

Директива 'use server' помечает функции как Server Actions — вызываемые с клиента, но выполняемые на сервере. Она не делает компонент Server Component. Server Components не нуждаются ни в какой директиве, поскольку они используются по умолчанию.

// Это Server Action, а не Server Component
async function submitForm(formData) {
  'use server'
  await db.insert({ email: formData.get('email') })
}

Server Actions — это фактически RPC-подобные эндпоинты. Относитесь к ним как к API-роутам: валидируйте входные данные, обрабатывайте ошибки и никогда не доверяйте данным от клиента.

Игнорирование модели кеширования RSC

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

// Явно указывайте намерения по кешированию
const data = await fetch(url, { 
  next: { revalidate: 3600 } // Кешировать на 1 час
})

// Или полностью отключите кеширование
const data = await fetch(url, { cache: 'no-store' })

Используйте revalidatePath() и revalidateTag() в Server Actions для инвалидации кешированных данных после мутаций. Модель кеширования RSC требует осознанных решений о свежести данных.

Заключение

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

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

Часто задаваемые вопросы

Частично. Server Components не могут использовать хуки состояния или эффектов, такие как useState или useEffect, потому что они выполняются только на сервере. Однако такие хуки, как useContext, поддерживаются. Если вашему компоненту нужно состояние, эффекты или браузерные API, вы должны добавить директиву use client, чтобы сделать его Client Component. Держите эти интерактивные части как можно меньшими и изолированными.

Спросите себя, нужна ли компоненту интерактивность, браузерные API или React-хуки вроде useState или useEffect. Если да, он должен быть Client Component. Если он только отображает данные или загружает их из базы данных, оставьте его как Server Component. В случае сомнений начните с Server Component и добавляйте use client только когда сборка или runtime явно этого требуют.

Наиболее частая причина — размещение use client слишком высоко в дереве компонентов. Когда родительский компонент становится Client Component, все его импорты попадают в клиентский бандл. Проверьте ваши директивы use client и опустите их до минимальных интерактивных компонентов. Также проверьте случайные импорты серверных библиотек в клиентском коде.

Директива use client помечает компонент для выполнения в браузере с доступом к хукам и браузерным API. Директива use server помечает функцию как Server Action, которая вызывается с клиента, но выполняется на сервере. Server Components вообще не нуждаются в директиве, поскольку они используются по умолчанию в Next.js App Router.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay