RSC: Когда серверные компоненты React становятся overengineering'ом

#react#rsc#performance

Последние полгода я собираю данные по использованию RSC в production — от небольших стартапов до enterprise-приложений. И всё чаще ловлю себя на мысли: мы применяем серверные компоненты как golden hammer, даже когда задача больше похожа на пластиковый конструктор.

Когда RSC действительно оправданы

В трёх сценариях серверные компоненты дают очевидные преимущества:

  1. Контентные сайты с SEO-зависимостью
    Рендеринг на сервере с встроенной streaming-загрузкой — это чистая магия для Time to First Byte. Но здесь RSC просто эволюция SSR, а не революция.

  2. Компоненты с тяжёлыми зависимостями
    Пример из моей практики: PDF-рендерер, который тащит 4MB WASM. Выносим его в RSC — и клиент получает уже готовую разметку без скачивания неподъёмного кода.

  3. Админки с динамической конфигурацией
    Если у вас интерфейс, где набор полей формы зависит от ACL, RSC позволяют собрать нужный UI до отправки клиенту.

// Пример: ACL-aware форма
async function AdminForm() {
  const userPermissions = await fetchPermissions();
  return (
    <form>
      {userPermissions.canEditName && <NameField />}
      {userPermissions.canUpload && <FileUploader />}
    </form>
  );
}

Where RSC creates more problems than solutions

Проблемы начинаются, когда мы пытаемся впихнуть RSC в сценарии, где они не дают реальных преимуществ:

1. Auth-walled applications
Для закрытых SPA (типа внутренних дашбордов) SSR-преимущества RSC почти не работают. Но мы всё равно плодим серверные компоненты, потому что “это модерно”.

2. Mobile-like experiences
В мобильных приложениях важна predictive prefetching. Но Next.js с его <Link prefetch> иногда ведёт себя как overeager intern — начинает грузить данные для страницы, до которой пользователь, возможно, никогда не долистает.

3. Over-fetching под маской прогрессивной загрузки
Типичный антипаттерн:

// Плохо: компонент грузит всё сразу
async function UserProfile() {
  const [user, posts, friends] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchFriends(),
  ]);
  return <ProfilePage {...{user, posts, friends}} />;
}

// Лучше: разделяем на клиентские запросы
function UserProfile() {
  const user = await fetchUser(); // RSC
  return (
    <ProfileLayout user={user}>
      <ClientPosts userId={user.id} /> // CSR
      <ClientFriends userId={user.id} /> // CSR
    </ProfileLayout>
  );
}

Архитектурная плата за RSC

Когда мы хвалимся уменьшением bundle size на клиенте, мы часто забываем о других издержках:

So what’s the move?

Мой текущий подход:

  1. Начинать с традиционного CSR/SSR микса
  2. Вводить RSC только для конкретных тяжёлых компонентов
  3. Для auth-walled SPA использовать RSC минимально — только там, где есть реальные требования к первичной загрузке

Самый честный знак, что вы переусердствовали с RSC — когда вам приходится добавлять "use client" на половине файлов в проекте. Это не failure, а сигнал, что архитектура требует пересмотра.

P.S. Если вашему приложению не нужен SEO и вы не боретесь за каждый килобайт бандла, возможно, вам вообще не нужны RSC. И это окей.


Источник: https://www.reddit.com/r/reactjs/comments/1vf42mc/are_we_overusing_rsc_for_problems_that_dont/