RSC вне Next.js: фреймворк — это бандлер
RSC — инфраструктура React, а не фича Next.js: биндинги react-server-dom говорят на Flight, плагин бандлера делит граф модулей и выдаёт клиентский манифест, клиентский рантайм потребляет поток, роутер перезапрашивает пейлоады. Дисциплина границы переносится всюду.
Архитектурное ревью свернуло не туда на одной фразе: «RSC — это штука Next.js, а Next мы не берём». Стафф-инженер в ответ открыл npm и показал react-server-dom-webpack — опубликован командой React, версионируется вместе с React, никакого Next.js внутри. Тигровая команда получила два спринта на «добавить RSC в наш Vite SPA, это же просто React». Первый спринт дал работающий Flight-поток в изоляции. Второй дал образование: их бандлер понятия не имел, что 'use client' должен резать сборку на два графа модулей; никто не выдавал манифест, который маппирует ссылки на модули из пейлоада в реальные URL чанков; dev-сервер падал, потому что под условием react-server не существует useState; а роутер невозмутимо делал полные перезагрузки страниц, потому что его не научили перезапрашивать пейлоады. Протокол Flight оказался лёгкими 10%. Команда провалилась не потому, что RSC доступен только в Next — это не так, — а потому, что остальные 90% реализации RSC живут в бандлере и роутере, то есть ровно в том, чем фреймворк и является. Понимать, где кончается React и начинается фреймворк, — и есть цель этого урока.
К концу урока ты сможешь точно назвать, где заканчивается React и начинается фреймворк — и объяснить, почему установки одного npm-пакета в существующий SPA недостаточно.
RSC — инфраструктура React, а не фича фреймворка
Протокол RSC и его эталонные реализации поставляются из самого репозитория React в виде биндингов под конкретные бандлеры: react-server-dom-webpack, с собратьями для других сборщиков (у Turbopack и Parcel свои пакеты; интеграция с Vite зрела отдельно как плагин). Серверная половина экспортирует функции вроде renderToPipeableStream — но из react-server-dom-webpack/server, и производит Flight-поток, а не HTML; клиентская половина даёт createFromFetch / createFromReadableStream, которые потребляют поток и вручают React дерево для рендера. Нигде здесь не упомянут Next.js. Что Next.js (и любой другой RSC-фреймворк) добавляет — это машинерия вокруг протокола, и недооценка этой машинерии — двухспринтовый урок из Hook.
Участвует и вторая сборка React: пакеты подписываются на условие экспорта react-server, и под ним react резолвится в сборку, где клиентские хуки попросту не существуют — useState в серверном компоненте падает на резолве модуля, а не из вежливости в рантайме. Ваш серверный и клиентский бандлы резолвят разный код для одного и того же импорта. Уже одна эта деталь дисквалифицирует подход «просто запустим один бандл с обеих сторон».
Четыре части минимальной RSC-инсталляции
Уберите весь фреймворк — несводимый чек-лист состоит из четырёх пунктов:
- Flight-сервер. Нечто, что берёт запрос, рендерит дерево серверных компонентов под условием
react-serverи стримит пейлоад. Десятки строк поверхreact-server-dom-webpack/server. - Плагин бандлера, ведущий два графа модулей. Он должен найти каждую директиву
'use client', разрезать граф в этом месте, собрать клиентскую сторону в чанки и — часть, о которой все забывают — выдать клиентский манифест: таблицу, маппирующую ссылки на модули из пейлоада ("./RangePicker.js") в чанки и имена экспортов, которые браузер должен загрузить. Ошибитесь в манифесте — и гидрация умрёт с «Could not find the module in the React Client Manifest». - Клиентский рантайм. Небольшой бутстрап, который забирает поток, прогоняет его через
createFromFetchи рендерит полученное дерево — обычно внутриstartTransition, чтобы подмена пейлоада не сносила незавершённый ввод. - Роутер, перезапрашивающий пейлоады. Навигация должна перехватывать клики по ссылкам, забирать Flight-пейлоад нового маршрута вместо HTML и реконсилировать его внутрь. Без этого у вас RSC-рендер первой загрузки и полные перезагрузки страниц навсегда — серверные компоненты «перерендериваются при навигации», только если что-то действительно так навигирует.
Пункты 2 и 4 — там, где живёт инженерия. Работа в бандлере инвазивна (два графа, перекрёстные ссылки между ними, синхронные манифесты dev- и prod-сборок, HMR (Hot Module Replacement — горячая замена модулей в dev-сервере), понимающий разрез), поэтому поддержка RSC приходит по-бандлерно и по-фреймворочно, а не одной установкой npm-пакета.
Вместе эти четыре пункта объясняют провал из Hook с хирургической точностью: у команды был пункт 1 (работающий Flight-поток), но не было пунктов 2, 3 и 4 — тех частей, что не являются протоколом, но делают его пригодным к использованию. Без манифеста ссылки из пейлоада никуда не резолвятся; без роутера клиентские компоненты не получают обновлений пейлоада; без условия react-server хуки просачиваются в неправильный бандл.
Команда ставит react-server-dom-webpack в существующий Vite SPA, добавляет 'use client' в несколько файлов и ждёт, что серверные компоненты заработают. Что на самом деле их блокирует?
Кто реализует это сегодня — честная карта
На начало 2026 года пейзаж, если судить по полноте, а не по маркетингу: Next.js App Router — самая полная продакшен-реализация и полигон с 2023 года: RSC, серверные функции, стриминг и кэширование сверху (кэширование — самая критикуемая часть, и это дизайн Next, а не требование RSC). Waku — минимальный эталон: небольшой фреймворк на Vite, построенный вокруг RSC by design; полезен и для лёгкого продакшена, и для чтения — самый чистый способ увидеть четыре части почти без всего остального. React Router (v7, наследующий линию Remix) ведёт работу по поддержке RSC как направлению-преемнику своей экосистемы. Parcel поставляет поддержку RSC со своими биндингами react-server-dom, а усилия вокруг Vite RSC-плагина зреют в сторону того, чтобы RSC стал возможностью бандлера, а не эксклюзивом фреймворка. Честное резюме: одна обкатанная боем реализация, несколько убедительных сходящихся и общий для всех слой протокола. Чего ждать не стоит: что RSC встанет в существующий CRA или голый webpack-SPA — CRA устарел и заморожен, его конфиг старше директивы; плагин бандлера вы писали бы сами, а это ровно та работа, которой вы пытались избежать.
Что переносится — и что покупает сложность
Всё из первого и второго уроков переносимо между фреймворками, потому что лежит на уровне протокола: серверные компоненты как слой данных с нулевым бандлом, 'use client' как разрез графа модулей, правила сериализации, DTO-дисциплина на границе, паттерн children, серверные функции как публичные эндпоинты. Выучите один раз — в Next, Waku, React Router и в чём угодно поверх Flight это одно и то же; различаются лишь файловые конвенции и семантика кэширования.
Абзац компромисса, которого заслуживает ваше архитектурное ревью: RSC берёт настоящий налог сложности — две среды исполнения, граница сериализации, способная утечь или упасть, инфраструктура, сцепленная с фреймворком, более трудная отладка (стек-трейс, начинающийся на сервере и кончающийся в hydration mismatch) и переобучение команды. Классический SPA + REST API сохраняет одну ментальную модель и позволяет маленькой команде просто выпускать; если ваш продукт — дашборд за логином, где бандл закэширован после первого визита, а SEO неважно, RSC может купить вам мало. Он окупается там, где его структура совпадает с продуктом: контентные и данные-тяжёлые страницы, чувствительная к бандлу аудитория, большие команды, выигрывающие от навязанного раздела данные/UI, и приложения, уже платящие за SSR-слой. Принять половину — дисциплину без инфраструктуры — бесплатно, а дисциплина и есть большая часть senior-ценности этого юнита.
В самодельной RSC-инсталляции гидрация падает с ошибкой: Could not find the module './Counter.js' in the React Client Manifest. Какая часть стека сломана и что она обязана делать?
- 01Назовите четыре части минимальной реализации RSC и укажите, где концентрируется настоящая инженерия — и почему её нельзя получить установкой npm-пакета.
- 02Дайте честную картину внедрения: кто реализует RSC сегодня и как маленькой команде взвешивать RSC против классического SPA с API?
RSC — инфраструктура, поставляемая из репозитория React, а не фича Next.js: биндинги react-server-dom реализуют протокол Flight — renderToPipeableStream на серверной стороне производит поток пейлоада, createFromFetch на клиентской его потребляет, — а условие экспорта react-server даёт серверным бандлам сборку React, где клиентские хуки даже не резолвятся. Полная реализация — четыре части: Flight-сервер; плагин бандлера, режущий граф модулей на каждой директиве use client и выдающий клиентский манифест, маппирующий ссылки из пейлоада в чанки; клиентский рантайм, превращающий поток обратно в дерево; и роутер, перезапрашивающий при навигации пейлоады вместо HTML. Бандлер и роутер — трудные 90%: двойные графы модулей, согласованность манифестов между dev и prod, HMR, понимающий разрез, — поэтому RSC нельзя npm-установить в SPA эпохи CRA, и поэтому поддержка приходит по-бандлерно и по-фреймворочно. Честная карта 2026 года: Next.js App Router — обкатанная боем реализация; Waku — минимальный читаемый эталон; React Router v7 несёт линию Remix к RSC; Parcel и зреющий Vite-плагин толкают RSC к статусу возможности бандлера. Через все из них переносится всё, чему научил этот юнит: модель двух сред, разрез графа модулей, правила сериализации и DTO-дисциплина, переплетение через children, серверные функции как публичные эндпоинты. И компромисс остаётся компромиссом: RSC облагает вас двумя средами, границей, способной утечь, и сцепкой с фреймворком — маленькая команда с дашбордом за логином может рационально остаться на SPA с API, а контентные, чувствительные к бандлу, уже платящие за SSR продукты — то место, где модель отрабатывает свою сложность. Теперь, когда услышишь дискуссию «а не взять ли RSC», ты сможешь разложить ровно то, на что команда подписывается: четыре части, где живёт инженерная цена — и какая половина сделки совпадает с их продуктом.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.