‘서버 컴포넌트’와 ‘클라이언트 컴포넌트’.
React Core Team은 이제 두 종류의 컴포넌트라는 개념을 손에 쥐었다. 하나는 서버라는 대지 위에 굳건히 서서 변치 않는 구조물을 만들고, 다른 하나는 클라이언트라는 물 위에서 사용자의 손짓에 따라 역동적으로 움직인다. 이 두 세계를 어떻게 하나의 React 생태계 안에서 자연스럽게 엮어낼 것인가. 이것이 그들 앞에 놓인 거대한 도전이었다.
“문제는 경계선이야.”
앤드류 클라크가 화이트보드에 두 개의 큰 원을 그리고, 각각 ‘Server’와 ‘Client’라고 적었다.
“서버 컴포넌트가 클라이언트 컴포넌트를 렌더링해야 할 때도 있고, 그 반대의 경우도 분명히 필요할 거야. 예를 들어, 서버에서 렌더링된 정적인 상품 상세 페이지(ProductPage.server.js
) 안에, 사용자와 상호작용하는 ‘장바구니 담기’ 버튼(AddToCartButton.client.js
)이 있어야만 해.”
그의 말대로였다. 두 세계는 서로를 완벽히 고립시킨 채 존재할 수 없었다. 반드시 서로를 호출하고, 데이터를 주고받는 통로가 필요했다.
“서버 컴포넌트가 클라이언트 컴포넌트를 렌더링하는 건 비교적 명확해 보입니다.” 조쉬가 의견을 냈다. “서버가 렌더링을 하다가 클라이언트 컴포넌트를 만나면, 그 자리에 ‘여기는 클라이언트 컴포넌트 자리임. 이 자바스크립트 파일을 다운로드해서 실행할 것’이라는 일종의 ‘표식’을 남기고 결과물을 보내주는 거지.”
그럴듯한 해결책이었다. 서버는 클라이언트에게 렌더링을 위임하는 지시서를 보내는 셈이다.
하지만 진짜 난관은 그 반대 방향이었다.
로렌이 날카로운 질문을 던졌다. “그럼, 클라이언트 컴포넌트가 서버 컴포넌트를 렌더링해야 할 때는 어떻게 하죠? 예를 들어, 클라이언트에서 동작하는 탭(Tab) UI가 있다고 쳐요. 사용자가 ‘프로필’ 탭을 누르면 서버에서만 렌더링되는 프로필 정보를, ‘설정’ 탭을 누르면 서버에서 렌더링되는 설정 정보를 보여주고 싶다면요?”
회의실에 다시 한번 정적이 흘렀다.
클라이언트 컴포넌트가 import ServerProfile from './Profile.server.js'
같은 코드를 실행하는 것은 불가능했다. 클라이언트의 자바스크립트 코드는 서버의 파일 시스템에 접근할 수도, 서버 환경에서 코드를 실행할 수도 없기 때문이다. 그것은 두 세계를 지탱하는 물리 법칙 자체를 위배하는 행위였다.
팀원들의 표정이 어두워졌다. 이 문제를 해결하지 못하면, 서버 컴포넌트는 그저 제한된 상황에서만 사용 가능한 반쪽짜리 기능으로 전락할 터였다.
그때, 세바스찬이 조용히 입을 열었다.
“우리는 관점을 바꿔야 해. 클라이언트 컴포넌트가 서버 컴포넌트를 ‘불러오는’ 게 아니야.”
그는 화이트보드에 다가가 로렌이 예로 든 탭 컴포넌트의 가상 코드를 그렸다.
“부모가 서버 컴포넌트여야 해. 그리고 그 부모가 렌더링될 때, 자식인 클라이언트 탭 컴포넌트에게 서버에서 미리 렌더링된 결과물을 ‘소품’처럼 전달해 주는 거지.”
// In a Server Component
<ClientTabs
profileTabContent={<ServerProfile />}
settingsTabContent={<ServerSettings />}
/>
코드를 본 팀원들의 눈이 번쩍 뜨였다.
클라이언트 컴포넌트는 자신이 무엇을 렌더링하는지 알 필요가 없었다. 그저 부모로부터 props
로 건네받은 결과물(children
또는 다른 prop)을 지정된 위치에 보여주기만 하면 됐다. 서버 컴포넌트를 렌더링하는 무거운 책임은 여전히 서버에 남아있고, 클라이언트 컴포넌트는 그저 전달받은 내용물을 보여주는 순수한 프레젠테이션 역할만 수행하는 것이다.
이것으로 규칙이 정해졌다.
- 서버 컴포넌트는 클라이언트 컴포넌트를 불러와 렌더링할 수 있다.
- 클라이언트 컴포넌트는 서버 컴포넌트를 직접 불러올 수 없다. 대신, 서버 컴포넌트가 렌더링한 결과를
props
로 전달받아 보여줄 수 있다.
이 비대칭적인 관계. 이것이 바로 두 세계를 무너뜨리지 않으면서도 조화롭게 연결하는 유일한 길이었다. 이로써 React는 서버에서 클라이언트로 흐르는 명확하고 예측 가능한 단방향 아키텍처의 기틀을 마련하게 되었다. 두 개의 세계는 공존할 수 있었고, 하나의 React라는 이름 아래 통합될 준비를 마쳤다.