Для вирішення типових завдань фронтенд-розробнику часто вистачає базових евристик: «компоненти за замовчуванням серверні», а для інтерактивності потрібно додати 'use client'. Проте для Lead-архітекторів та Senior-інженерів поверхневого розуміння недостатньо.
Коли додаток масштабується до сотень маршрутів і мільйонів користувачів, неминуче виникають складні низькорівневі питання:
Date, BigInt, Map, Set, нерозв'язані Promise) вільно перетинають межу мережі, а функції та класи викликають фатальні помилки серіалізації?Глибоке розуміння React Flight Protocol та внутрішньої будови збирача Turbopack дозволяє проєктувати високопродуктивні системи з мінімальним оверхедом і блискавично локалізувати архітектурні збої в продакшені.
Рендеринг і доставка React Server Components — це не одноразова генерація HTML, а потоковий конвеєр, що зв'язує два рантайми (Node.js/Edge та Browser).
При первинному вході на сайт сервер віддає HTML-каркас (для миттєвого відображення першого контенту FCP) разом із потоком Flight Payload.
Проте при подальших переходах користувача за посиланнями <Link> браузер не перезавантажує HTML-сторінку. Натомість ініціюється Soft Navigation (М'яка навігація): браузер відправляє фоновий мережевий запит із заголовком RSC: 1 (або параметром ?_rsc=...), отримуючи чистий потік інструкцій Flight.
КЛІЄНТ (Браузер) СЕРВЕР (Node.js/Edge)
│ │
(1)│── Клік по посиланню <Link href="/analytics"> ────►│
│ GET /analytics?_rsc=123 (Заголовок RSC: 1) │
│ │
(2)│ │ 1. Виконання Server Components
│ │ 2. Серіалізація у Flight Protocol
│ │ - Resource Hints: H
│ │ - Client References: I
│ │ - UI Virtual Tree: J
│ │
(3)│◄── Потік RSC Flight Payload (Chunks) ─────────────┤
│ │
(4)│ 1. Парсинг потоку Flight │
│ 2. Turbopack довантажує потрібні JS-чанки (I) │
│ 3. React Reconciler узгоджує віртуальний DOM │
│ 4. Оновлення UI БЕЗ перезавантаження сторінки! │
│ (Стейт активних Client Components збережено) │Сервер запускає функції компонентів, розв'язує асинхронні запити до БД та генерує віртуальне дерево елементів.
Дерево перетворюється на рядковий потік. Серверні вузли стають JSON-тегами, а клієнтські — Client References дескрипторами.
Рантайм React інспектує передані пропси. Якщо в потік потрапляє об'єкт із позначкою taint, серіалізація переривається помилкою безпеки.
Чанки Flight передаються через HTTP-стрім. Браузер починає обробку перших рядків до завершення рендерингу всієї сторінки.
React на клієнті вбудовує нове піддерево в живий DOM, звіряючи ключі з Client Component Manifest, та активує інтерактивність.
Що запитує браузер у сервера, коли користувач клікає по внутрішньому посиланню <Link href='/analytics'> в Next.js App Router?
У мережевому потоці Flight Payload являє собою набір текстових рядків, розділених символом переведення рядка \n. Кожен рядок починається з префікса, що визначає тип інструкції:
H["/static/css/app.css","stylesheet"]
I["(app-pages-browser)/./components/Counter.tsx",["static/chunks/counter.js"],"Counter"]
J0:["$","div",null,{"className":"container","children":[
["$","h1",null,{"children":"Панель керування"}],
["$","$L1",null,{"initialCount":10}]
]}]H (Resource Hint): Resource Hint для негайного фонового завантаження стилів та шрифтів.I (Client Module Import): Запис Client Component Manifest, що вказує браузеру фізичний шлях до JavaScript-чанка клієнтського компонента.J (JSON Element Tree): Серіалізоване дерево елементів. Символ $ позначає React.createElement, а $L1 вказує на клієнтський модуль із рядка I.Аналізуйте сирий бінарно-текстовий протокол серіалізації React Server Components
Запитує Flight stream замість повного HTML
Передає клієнтський знімок для дифу дерева
Прапорець передзавантаження через <Link>
1:I["./components/MetricCard.tsx",["chunks/metric_card_client.js"],"default"]0:{"name":"AnalyticsPage","children":["$","div",null,{"className":"p-6","children":["$","$L1",null,{"metric":"visitors"}]}]}2:L1:{"value":14280,"status":"fresh","timestamp":1741859200}Сервер не відправляє вихідний код MetricCard. Натомість він передає покажчик на бандл-чанк, який браузер має завантажити та гідратувати.
Решта 90% коду сторінки залишається на сервері без роздування клієнтського бандлу (Zero-Bundle Size).
React Flight нативно підтримує Date та BigInt. Ніколи не використовуйте деструктивний хак JSON.parse(JSON.stringify())! Проєктуйте вибірку через явний типізований DTO:
export interface OrderDTO {
id: string;
totalAmount: bigint; // Flight нативно серіалізує BigInt!
createdAt: Date; // Flight нативно серіалізує Date!
metadata: Map<string, string>; // Flight нативно серіалізує Map!
}
export function toOrderDTO(raw: {
id: string;
total: bigint;
created_at: Date;
meta: Record<string, string>;
}): OrderDTO {
return {
id: raw.id,
totalAmount: raw.total,
createdAt: raw.created_at,
metadata: new Map(Object.entries(raw.meta)),
};
}import { db } from '@/lib/db';
import { toOrderDTO } from '@/lib/dto/order';
import { ClientOrderView } from './ClientOrderView';
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function OrderPage({ params }: PageProps) {
const { id } = await params;
const rawOrder = await db.order.findUniqueOrThrow({ where: { id } });
// Перетворюємо на безпечний DTO з нативними типами
const orderDTO = toOrderDTO(rawOrder);
// Передаємо клієнту: Date, BigInt та Map збережуться без втрат!
return <ClientOrderView order={orderDTO} />;
}Коли функція з директивою 'use server' передається в клієнтський компонент (наприклад, через .bind()), рантайм React шифрує прив'язані аргументи за допомогою Action Closure Encryption:
import { deleteUserAction } from '@/app/actions/user';
import { DeleteUserButton } from './DeleteUserButton';
export default async function UsersPage() {
const userId = 'usr_98741';
// .bind() шифрує аргументи в закритий криптографічний токен усередині Flight Payload!
// Клієнт бачить лише непрозоре RPC-посилання вигляду: $F1 = ["$ACTION_ID_123", ["usr_98741"]]
const boundDelete = deleteUserAction.bind(null, userId);
return <DeleteUserButton onDelete={boundDelete} />;
}Чому виклик JSON.parse(JSON.stringify(data)) перед передачею даних із Server Component у Client Component вважається шкідливим антипатерном у сучасній розробці?
Спроба «очистити» об'єкти перед передачею клієнту через JSON.parse(JSON.stringify(data)) є грубою помилкою:
Date перетворюються на рядки ISO 8601, вимагаючи повторного new Date() на клієнті.BigInt викидають фатальний TypeError: Do not know how to serialize a BigInt.Map та Set перетворюються на порожні об'єкти {}.React Flight нативно підтримує Date, BigInt, Map, Set та ArrayBuffer!
Рішення: Проєктуйте вибірку через типізовані DTO без використання JSON.stringify.
Коли ви прив'язуєте аргументи до Server Action для передачі в Client Component, Next.js зашифрує всі прив'язані аргументи та передасть їх клієнту всередині рядка W протоколу Flight. Якщо замкнути масив на 10 000 елементів або секретний ключ, бандл Flight роздується на мегабайти, а конфіденційні дані будуть відправлені на пристрій користувача.
Рішення: Передавайте в .bind() лише публічні скалярні ідентифікатори (ID сутності), а секретні ключі витягуйте на сервері безпосередньо всередині тіла Server Action.
// ❌ НЕБЕЗПЕЧНО: передача приватного ключа в bound action
// const boundAction = deleteUser.bind(null, userId, secretApiKey);
// ✅ ПРАВИЛЬНО: передавати лише публічний ID
// const boundAction = deleteUser.bind(null, userId);Користувач відкрив сайт на версії деплою v1. Ви викатили v2, де хеші JS-чанків змінилися. Користувач клікає по посиланню:
?_rsc=... та отримує Flight Payload від v2.I з хешем нового чанка chunk-v2-abc.js.Як Next.js вирішує це під капотом: Next.js перехоплює збій узгодження версій та автоматично ініціює Hard Navigation (повне перезавантаження документа), непомітно оновлюючи вкладку користувача до версії v2.
Об'єкти, десеріалізовані з Flight Payload, заморожуються React (Object.freeze). Спроба напряму змінити властивість props.user.name = 'New' викине помилку в strict mode або призведе до неконсистентного стану Reconciler при повторному візиті на сторінку.