Search
🤔

제7장: Fizz와 Flight - BigPipe의 귀환과 서버 컴포넌트 혁명 (2016-2023)

프롤로그: 2016년 5월, 은밀한 시작

2016년 5월 11일, React 저장소에 조용한 Pull Request가 올라왔습니다.[^134-1]
제목은: "Initial PR for async architecture (Fiber)"
작성자는 Andrew Clark. 하지만 설계자는 따로 있었어요. Sebastian Markbåge.[^135-1]
Sebastian은 누구였을까요? 스웨덴 출신의 개발자로, 90년대부터 웹을 만들어왔어요.[^136-1] 2006년, 클라이언트 사이드 JavaScript가 부활할 때 MooTools 커뮤니티에서 활동했죠. 그곳에서 평생 친구들을 만들었습니다.[^136-1]
2012년경, 그는 Facebook에 입사했어요.[^136-1] Jordan Walke가 React 초기 버전을 개발하고 있을 때였죠. Sebastian은 React를 보자마자 직감했습니다: "이거다."
그는 심리학을 전공했어요.[^137-1] 그래서 그런지 그의 말은 항상 "몇 달 후에야 이해가 되는" 것으로 유명했죠.[^137-1]
하지만 그의 비전은 명확했습니다: "React는 단순히 UI 라이브러리가 아니다. 완전히 새로운 렌더링 모델이다."

1막: Fiber - 쪼갤 수 있는 렌더링 (2016-2017)

문제의 발견

2015-2016년, React는 거대한 문제에 직면했습니다.[^138-1]
JavaScript는 단일 스레드예요. React가 큰 컴포넌트 트리를 렌더링하면? 메인 스레드를 블로킹했죠. 애니메이션이 끊기고, 입력이 먹통이 되고, 사용자 경험이 망가졌습니다.[^138-1]
// 이런 코드가 문제였어요 function BigComponent() { return ( <div> {Array.from({ length: 10000 }, (_, i) => ( <ExpensiveChild key={i} /> ))} </div> ) } // 10000개를 한 번에 렌더링 → 메인 스레드 블로킹!
JavaScript
복사
Sebastian은 생각했습니다:[^138-1]
브라우저는 60fps를 유지하려면 각 프레임을 16ms 안에 그려야 해요. 렌더링을 16ms 청크로 쪼갤 수 있다면? 더 중요한 작업(사용자 입력)을 먼저 처리하고, 덜 중요한 작업(백그라운드 업데이트)은 나중에? **우선순위 기반 스케줄링**이 필요해요.
Plain Text
복사

Fiber의 탄생

Fiber는 "렌더링을 중단 가능하게 만드는" 새로운 reconciler였습니다.[^138-1]
핵심 아이디어:[^138-1]
렌더링 작업을 작은 단위로 쪼갠다
각 단위마다 브라우저에게 제어권을 돌려준다
긴급한 작업(클릭 이벤트)이 들어오면 렌더링을 멈추고 먼저 처리한다
그 다음 렌더링을 재개한다
2017년 9월, React 16이 릴리즈됐고 Fiber가 들어갔습니다.[^138-1]
사용자들은 몰랐어요. 겉보기엔 똑같았으니까요. 하지만 내부는 완전히 다시 써졌죠.
이건 다음 단계를 위한 토대였습니다.

2막: Suspense와 Concurrent Mode - 기다림의 예술 (2018)

2018년 10월, React Conf

React Conf 2018. 이틀째 아침.[^139-1][^140-1]
Andrew Clark와 Brian Vaughn이 무대에 올랐습니다.
제목: "Concurrent Rendering in React"
Andrew가 말했습니다:[^140-1][^141-1]
"개발자로서 우리는 앱을 빠르게 만들려다가 오히려 사용자 경험을 망치곤 합니다. 코드 스플리팅을 하면? 스피너가 깜빡입니다. 데이터를 페칭하면? 또 스피너. 스피너 지옥이죠."
그리고 데모를 보여줬어요:[^140-1][^141-1]
function ProfilePage() { return ( <Suspense fallback={<Spinner />}> <ProfileDetails /> <Suspense fallback={<PostsGlimmer />}> <Posts /> </Suspense> </Suspense> ) }
JavaScript
복사
<Suspense>. 컴포넌트가 데이터를 기다리는 동안 fallback을 보여주는 경계선.[^140-1][^141-1]
하지만 진짜 마법은 Concurrent Mode와 결합될 때였어요:[^141-1]
빠른 네트워크? 스피너를 건너뛰고 바로 콘텐츠 표시
느린 네트워크? 설정한 threshold(예: 1초) 이상 기다릴 때만 스피너 표시
탭 전환? 다음 탭을 미리 렌더링해두고, 사용자가 전환하면 즉시 표시
깜빡이는 스피너 문제가 사라졌습니다.[^140-1][^141-1]
청중은 감탄했어요. 하지만 한 가지가 빠져있었죠.
서버 사이드 렌더링은?

3막: BigPipe의 귀환 - Fizz (2018-2021)

잊혀지지 않은 아이디어

Sebastian Markbåge는 Facebook에서 일했어요. 그는 BigPipe를 알고 있었죠.[^142-1]
2010년, Changhao Jiang이 만든 그 기술. HTML을 스트리밍하는 방법.[^142-1]
클라이언트가 요청 → 서버가 페이지의 위쪽부터 HTML을 만들며 보냄 → 느린 부분은 placeholder → 나중에 준비되면 그 부분의 HTML을 추가로 보냄 → 작은 script로 placeholder를 교체
Plain Text
복사
Sebastian은 생각했습니다: "이걸 React로 할 수 있지 않을까?"[^143-1]

2018년, 코드명 Fizz

React 팀은 조용히 새 프로젝트를 시작했어요.[^143-1]
코드명: Fizz
목표: BigPipe + React + Suspense = Streaming SSR
기존의 renderToString은 동기적이었어요:[^143-1]
const html = renderToString(<App />) // 전체 트리가 렌더링될 때까지 기다림
JavaScript
복사
Fizz는 비동기적이었죠:[^143-1][^144-1]
const { pipe } = renderToPipeableStream(<App />, { onShellReady() { // 위쪽(shell)이 준비되면 스트리밍 시작 res.setHeader('content-type', 'text/html') pipe(res) } })
JavaScript
복사

Suspense + Streaming의 마법

function App() { return ( <html> <body> <Header /> {/* 빠름 */} <Suspense fallback={<Spinner />}> <SlowData /> {/* 느림 */} </Suspense> <Footer /> {/* 빠름 */} </body> </html> ) }
JavaScript
복사
무슨 일이 일어날까요?[^144-1][^145-1]
1. 서버가 Header를 렌더링 → 즉시 클라이언트로 전송 2. SlowData를 기다리는 동안 Spinner HTML 전송 3. Footer도 렌더링 → 전송 4. 사용자는 Header와 Footer를 바로 볼 수 있음! 5. SlowData 준비됨 → HTML + 작은 script 전송 6. Script가 Spinner를 실제 데이터로 교체
Plain Text
복사
BigPipe가 React로 돌아왔습니다.[^144-1][^145-1]

2021년, React 18

2021년 6월, React 18 beta가 발표됐습니다.[^146-1]
새로운 API:[^146-1]
renderToPipeableStream (Node.js용)
renderToReadableStream (Web Streams용)
Selective Hydration
Selective Hydration이 특히 혁신적이었어요:[^146-1]
기존: 전체 페이지를 hydrate한 후에야 인터랙티브 새로운 방식: 일부만 hydrate되어도 그 부분은 인터랙티브
사용자가 버튼을 클릭하면? React가 그 부분을 우선 hydrate합니다.[^146-1]
하지만... 여전히 한계가 있었어요.

4막: 근본적인 질문 - Server Components (2019-2020)

2019년, Sebastian의 질문

Sebastian Markbåge가 React 팀에 질문을 던졌습니다:[^147-1][^148-1]
"우리는 2013년부터 'isomorphic'을 외쳤습니다. 같은 코드를 서버와 클라이언트에서 돌리자고요. 하지만... 이게 정말 맞는 방향일까요?"
문제는 명확했어요:[^147-1][^148-1]
// Next.js에서 이렇게 해야 했죠 export async function getServerSideProps() { const db = require('database') const posts = await db.query('SELECT * FROM posts') return { props: { posts } } } function Page({ posts }) { // 컴포넌트는 데이터만 받음 // 데이터 페칭 로직과 분리됨 return <PostList posts={posts} /> }
JavaScript
복사
왜 컴포넌트 안에서 직접 못할까요?[^147-1]
// 이렇게 하고 싶었어요 async function Page() { const db = require('database') // ❌ 클라이언트에서 실행 불가! const posts = await db.query('SELECT * FROM posts') return <PostList posts={posts} /> }
JavaScript
복사

패러다임의 전환

Sebastian과 Andrew Clark, Dan Abramov, Joe Savona는 논의했습니다.[^147-1][^148-1]
"같은 코드를 양쪽에서 돌릴 필요가 없다면?"[^147-1]
컴포넌트를 두 종류로 나누는 거예요:[^147-1][^148-1]
Server Components:
서버에서만 실행
데이터베이스 직접 접근 가능
큰 라이브러리 사용해도 번들 크기 증가 없음
코드가 클라이언트로 전송되지 않음
Client Components:
클라이언트에서 실행
인터랙티브
기존 React 컴포넌트와 동일
그런데 어떻게 이 둘을 섞을까요?

Flight - 가상 DOM의 직렬화

핵심 아이디어:[^147-1][^148-1]
1. 서버에서 Server Component를 렌더링 2. 가상 DOM을 직렬화 (JSON-like 형식) 3. 네트워크로 전송 4. 클라이언트가 받아서 실제 DOM으로 변환
Plain Text
복사
이것이 Flight였습니다. React Server Components의 transport layer.[^147-1]
예시:[^148-1]
// Note.server.js import db from 'database' // ✅ 서버에서만 실행 export default async function Note({ id }) { const note = await db.notes.get(id) return ( <div> <h1>{note.title}</h1> <p>{note.body}</p> <NoteEditor note={note} /> {/* Client Component */} </div> ) } // NoteEditor.client.js 'use client' // Client Component 표시 export default function NoteEditor({ note }) { const [text, setText] = useState(note.body) return ( <textarea value={text} onChange={(e) => setText(e.target.value)} /> ) }
JavaScript
복사
서버가 보내는 데이터:[^147-1][^148-1]
{ "type": "div", "children": [ { "type": "h1", "children": "Meeting Notes" }, { "type": "p", "children": "Discuss Q4 plans..." }, { "type": "NoteEditor", "props": { "note": { "body": "..." } } // NoteEditor는 Client Component이므로 클라이언트에서 hydrate } ] }
JSON
복사

2020년 12월 21일, 공개

크리스마스 전.[^149-1]
Dan Abramov, Lauren Tan, Joseph Savona, Sebastian Markbåge가 글을 올렸습니다.[^149-1]
제목: "Introducing Zero-Bundle-Size React Server Components"
Server Components는 아직 연구 개발 중입니다. 투명성의 정신으로 공유합니다. 서둘러 배울 필요 없어요!
Plain Text
복사
영상과 데모, RFC를 공개했죠.[^149-1]

커뮤니티의 반응

혼란:[^149-1][^150-1]
"Server Components vs SSR?" "Client Component는 기존 컴포넌트?" "어떻게 섞어?" ".server.js 파일?"
Dan Abramov는 Twitter에서 몇 달간 질문에 답했습니다.[^150-1]
핵심 정리:[^150-1][^151-1]
Server Components ≠ SSR (보완 관계)
SSR: HTML을 빨리 보여줌
Server Components: JavaScript 번들 크기 줄임
함께 쓸 수 있음
하지만 실제로 쓸 수 있는 건 아직 멀었어요.

5막: 통합 - Next.js 13 App Router (2021-2023)

Vercel과의 협업

Vercel 팀(Shu Ding, Jiachi Liu, Tobias Koppers, Tim Neutkens)이 참여했습니다.[^147-1]
그들은 React Server Components를 프로덕션에 통합하는 첫 번째 프레임워크를 만들기로 했죠.
2년간의 작업.[^147-1]
왜 이렇게 오래 걸렸을까요?
번들러 통합이 악몽이었거든요.[^147-1]
Server Component에서 Client Component import → 번들에 포함 Client Component에서 Server Component import → 안 됨! 파일 확장자로 구분? (.server.js, .client.js) → 개발자들이 싫어함 새로운 방법: 'use client' directive
Plain Text
복사

2022년 10월 25일, Next.js Conf

6년 전 Next.js를 발표했던 바로 그날.[^152-1]
Vercel이 발표했습니다: Next.js 13
App Router - React Server Components 기반의 새로운 라우팅 시스템.[^152-1]
// app/page.js (Server Component by default!) import db from '@/lib/db' export default async function Page() { // ✅ 컴포넌트 안에서 직접 데이터 페칭! const posts = await db.query('SELECT * FROM posts') return ( <div> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ) } // app/post/[id]/page.js async function PostPage({ params }) { const post = await db.posts.get(params.id) return ( <article> <h1>{post.title}</h1> <LikeButton postId={post.id} /> {/* Client Component */} </article> ) }
JavaScript
복사
드디어. 2013년부터 원했던 게 가능해졌습니다.

2023년, 혼란과 분열

하지만 커뮤니티는 분열됐어요.
찬성파:
"드디어 제대로 된 SSR이다"
"번들 크기가 극적으로 줄었다"
"PHP로 회귀? 아니, 더 나은 형태로 진화다"
반대파:[^153-1]
"너무 복잡하다"
"Server/Client 구분이 헷갈린다"
"use client를 언제 써야 하나?"
"디버깅이 어렵다"
다른 프레임워크들의 반응:
Remix (Ryan Florence, Michael Jackson):[^153-1]
"우리는 웹 표준에 충실하겠다"
"Form, loader, action - 브라우저가 이미 하는 방식"
React Server Components는 지원 안 함 (2023년 기준)
SvelteKit, SolidStart:
각자의 서버 컴포넌트 모델 개발
React Router v7 (2024):[^153-1]
Single Fetch 패턴
RSC 지원하지 않기로 결정

Sebastian Markbåge, Vercel로

2023년 6월, 놀라운 소식.[^154-1]
Sebastian Markbåge가 Vercel에 입사했습니다.
하지만 여전히 React Core Team의 리더십을 유지하기로 했죠.[^154-1]
이건 큰 신호였어요: Vercel과 React의 긴밀한 협력.

에필로그: 끝나지 않은 여정

2024-2025년 현재.
React Server Components는 여전히 논란적입니다.
해결된 것:[^147-1][^148-1][^149-1]
renderToString의 블로킹 → renderToPipeableStream으로 해결
Isomorphic의 제약 → Server Components로 깨짐
BigPipe의 아이디어 → Suspense + Streaming으로 부활
번들 크기 문제 → Server Components로 해결
여전히 어려운 것:
높은 학습 곡선
복잡한 정신 모델
디버깅의 어려움
생태계 분열

역사의 교훈

2013년, React는 "같은 코드를 양쪽에서"라고 말했어요.[^155-1]
2023년, React는 "다른 코드를 다른 곳에서"라고 말합니다.[^147-1][^148-1]
둘 다 맞았을까요? 아니면 둘 다 틀렸을까요?
아마도 답은: 시대마다 다른 정답이 있다는 것.
2010년: BigPipe (HTML 스트리밍) 2013년: Isomorphic (같은 코드) 2018년: Fizz (React 스트리밍) 2020년: Flight (서버 컴포넌트) 2023년: App Router (전부 통합)
각 단계는 이전 단계의 한계를 극복하려는 시도였습니다.
그리고 모든 혁신의 중심에는 한 사람이 있었어요.
Sebastian Markbåge.
그는 심리학을 전공했죠.[^137-1] 그래서 그런지 그의 질문은 항상 근본적이었습니다:
"왜 우리는 이렇게 하고 있지?" "더 나은 방법은 없을까?" "사용자 경험을 진짜로 개선하려면?"
BigPipe → Fiber → Suspense → Fizz → Flight
이 모든 여정은 하나의 질문에서 시작됐습니다:
"어떻게 하면 웹을 더 빠르게 만들 수 있을까?"
다음 장 예고:
2023-2024년, 개발자들은 혼란 속에 있었습니다.
"Server Components를 써야 하나?" "Remix가 맞나?" "아니면 SvelteKit?" "또는 그냥 MPA(Multi-Page App)로 돌아갈까?"
SSR의 미래는 어디로 가고 있을까요?
그리고 우리는 정말로 "PHP로 회귀"하고 있는 걸까요?
아니면 나선형으로 올라가고 있는 걸까요?
참고 문헌 표기:
[^134-1]: Fatih's Blog, "The Suspense is Killing Me: Part 1" - React Fiber timeline [^135-1]: React Blog, "React Conf recap: Hooks, Suspense, and Concurrent Rendering" (2018) [^136-1]: Patrick Aljord, "ReactEurope interview #17: Sebastian Markbåge", Medium (2015) [^137-1]: React Team page, "Meet the Team - Sebastian Markbåge" [^138-1]: Fatih's Blog, "The Suspense is Killing Me: Part 1" - Fiber and time-slicing [^139-1]: React Blog, "React Conf recap: Hooks, Suspense, and Concurrent Rendering" (2018) [^140-1]: FreeCodeCamp, "Lessons Learned at React Conf 2018" (2019) [^141-1]: Auth0 Blog, "Beyond React 16: Time Slicing and Suspense API" [^142-1]: Facebook Engineering, "BigPipe: Pipelining web pages for high performance" (2010) [^143-1]: GitHub reactwg/react-18, "Upgrading to React 18 on the server" (#22) [^144-1]: LogRocket Blog, "A guide to streaming SSR with React 18" (2024) [^145-1]: Prateek Surana, "The future of rendering in React" [^146-1]: React Documentation, "renderToPipeableStream" [^147-1]: React RFC, "Server Components" (#188) [^148-1]: React Blog, "Introducing Zero-Bundle-Size React Server Components" (2020) [^149-1]: React Blog, "Introducing Zero-Bundle-Size React Server Components" (2020) [^150-1]: FreeCodeCamp, "What are React Server Components?" (2021) [^151-1]: Patterns.dev, "React Server Components" [^152-1]: Next.js 13 announcement (2022) [^153-1]: Various community responses on Twitter, Reddit, and Hacker News (2023-2024) [^154-1]: Vercel Blog, "Supporting the Future of React" (2023) [^155-1]: Airbnb Rendr announcement (2013)