Flight Protocol
Next.js App Router와 React Server Components(RSC) 아키텍처를 다루다 보면 '스트리밍', 'RSC 페이로드' 등의 용어와 함께 데이터가 서버에서 클라이언트로 어떻게 전달되는지 궁금해질 때가 있습니다. 이번 글에서는 서버에서 렌더링된 React 트리가 브라우저로 전달되는 기술인 Flight Protocol(플라이트 프로토콜) 의 개념과 동작 원리 정리해 보겠습니다.
1. Flight Protocol이란?
Flight는 React Server Components가 서버에서 클라이언트로 React 컴포넌트 트리를 직렬화(Serialize)하여 통째로 전송할 때 사용하는 전용 데이터 포맷입니다.
데이터가 서버에서 클라이언트로 '비행(flight)'한다는 직관적인 비유에서 출발한 React 팀의 내부 코드네임이며, 실제 Next.js나 React 코어 코드베이스에서도 ReactFlightServer와 같은 이름으로 핵심 모듈에 사용되고 있습니다.
2. 왜 JSON만으로 부족할까?
일반적으로 서버와 클라이언트 간의 데이터 교환에는 JSON이 쓰입니다. 하지만 JSON은 문자열, 숫자, 배열, 객체와 같은 순수한 데이터 구조만 표현할 수 있습니다.
서버에서 렌더링된 복잡한 React 트리를 클라이언트에 전달하기 위해서는 단순한 데이터를 넘어 모듈 시스템과 React의 동작 모델을 이해하는 특수한 포맷이 필요합니다. Flight Protocol은 JSON을 확장하여 다음과 같은 요소들을 직렬화할 수 있도록 설계되었습니다:
- React Element:
<div>나<MyComponent>같은 리액트 노드 - Client Component 참조: 초기 로딩 시 코드를 포함하지 않고, 브라우저가 나중에 불러올 수 있도록 모듈의 위치(Import path)를 지시
- Suspense 경계 마커 및 Promise: 비동기적으로 준비되는 데이터를 처리하기 위한 상태
- Server Action 함수 참조: 클라이언트에서 폼을 제출할 때 호출할 서버 함수의 식별자
3. Flight 페이로드의 구조와 동작 방식
Flight 페이로드는 거대한 단일 JSON 덩어리가 아니라, 줄바꿈으로 구분된 라인(Line) 기반의 스트림 포맷(ND-JSON 유사) 으로 구성됩니다. 각 라인이 하나의 청크(Chunk) 역할을 하므로, 서버에서 렌더링이 완료되는 조각부터 브라우저로 점진적 스트리밍(Streaming)이 가능합니다.
실제 데이터를 살펴보면 특수한 접두사(Prefix)들을 확인할 수 있습니다:
0:["$","main",null,{...}]:$기호는React Element를 의미합니다.<main>태그 트리를 나타냅니다 .1:I["./Counter.tsx", ...]:I기호는 Import(클라이언트 컴포넌트 참조) 를 의미합니다.
여기서 주의할 점 (오해하기 쉬운 개념) Flight 페이로드 안에는 Client Component의 실제 자바스크립트 코드가 섞여 있지 않습니다. 대신 "이 위치에 Counter.tsx 모듈을 동적으로 가져와서 렌더링하라"는 참조(Reference) 마커만 전달됩니다. 브라우저는 이 마커를 해석하여 필요한 JS 번들을 별도로 다운로드하고 실행합니다.
4. 렌더링 시점에 따른 전송 메커니즘
사용자의 행동에 따라 Flight 데이터가 브라우저로 전달되는 방식이 달라집니다.
- 초기 페이지 로드 (Initial Load) 사용자가 처음 URL에 접속하면 서버는 완성된 HTML 문서를 스트리밍합니다. 이때 HTML 문서 하단에
<script>self.__next_f.push([...])</script>형태로 Flight 페이로드가 인라인(embed)되어 함께 전달됩니다. 브라우저는 수신한 HTML을 즉시 페인트하여 사용자에게 화면을 보여주고, 클라이언트 측 React 런타임은 백그라운드에서 이 Flight 배열을 읽어들여 내부 컴포넌트 트리를 재구성합니다. - 페이지 이동 (SPA Navigation) 클라이언트에서
<Link>컴포넌트를 클릭해 다른 페이지로 이동할 때는 브라우저가 전체 HTML을 새로 요청하지 않습니다. 클라이언트 라우터가fetch('/posts/1?_rsc=...')형태로 서버에 요청을 보내고, 서버는 순수한 Flight 페이로드만을 응답합니다. 클라이언트는 이 직렬화된 데이터를 받아 브라우저 메모리 상의 '라우터 캐시(Router Cache)'에 병합하고, 변경된 세그먼트만 부분적으로 교체하여 화면을 업데이트합니다.
결론
HTML이 브라우저가 즉시 화면에 그릴 수 있도록 렌더링이 완료된 '결과물'이라면, Flight Protocol은 클라이언트의 React가 컴포넌트 트리를 어떻게 복원하고 어느 부분을 동적으로 불러올지 알려주는 정교한 '명세서' 라고 할 수 있습니다.
이러한 라인 기반의 직렬화 포맷 덕분에 Next.js는 무거운 연산을 서버에 남겨두고 결과물만을 조각조각 스트리밍하며 클라이언트의 자바스크립트 부담을 줄이는 최적화된 라우팅 경험을 제공할 수 있습니다. Network 탭에서 ?_rsc= 요청을 마주친다면, 이제 그 안에 담긴 문자열 스트림이 React 트리를 재구성하는 설계도라는 점을 떠올려 보시기 바랍니다.