누군가 RSC가 무엇이냐?라 묻는 질문에 대한 답을 한 번 생각해 봤습니다.
그에 대한 저의 대답은 "RSC는 서버에서 렌더링 되는 새로운 컴포넌트로, 빌드 시 JSX 트리를 구성하여 HTML을 산출한다."입니다. 어떻게 이렇게 대답할 수 있을까요?
자, 우선 RSC가 일반적인 리액트 컴포넌트와 어떻게 다른지 보겠습니다. 아래는 일반적인 컴포넌트와 서버 API입니다.
클라이언트가 요청을 보내면, 서버는 db에서 데이터를 찾아서 응답합니다. 클라이언트는 이 데이터를 받아서 렌더링하죠.
// bundle.js
function Note({id}) {
const [note, setNote] = useState('');
// NOTE: loads *after* first render.
useEffect(() => {
fetch(`/api/notes/${id}`).then(data => {
setNote(data.note);
});
}, [id]);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
// api
import db from './database';
app.get(`/api/notes/:id`, async (req, res) => {
const note = await db.notes.get(id);
res.send({note});
});
이와 똑같이 동작하는 코드를 보겠습니다. 한눈에 봐도 코드의 양이 엄청나게 줄었음이 느껴집니다!
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
비동기 컴포넌트가 DB에서 직접 데이터를 찾아서 렌더링 하고 있습니다. 심지어 데이터에 대한 접근이 렌더링과 함께 일어납니다.
어떻게 클라이언트에서 직접 DB 데이터에 접근할 수 있을까요? 이면에 더 중요한 무언가가 숨겨져 있지 않을까요?
RSC란?
이 컴포넌트의 이름은 리액트 서버 컴포넌트(React Server Components)입니다.
리액트 공식 문서에 따르면 서버 컴포넌트를 다음과 같이 설명합니다.
Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server.
This separate environment is the “server” in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server.
(서버 컴포넌트는 번들링 전에 클라이언트 앱 또는 SSR 서버와 별도의 환경에서, 미리 렌더링하는 새로운 유형의 컴포넌트입니다.
이 별도의 환경이 리액트 서버 컴포넌트에서 "서버"입니다. 서버 컴포넌트는 CI 서버에서 빌드 시 한 번 실행하거나 웹 서버를 사용하여 각 요청에 대해 실행할 수 있습니다.)
클라이언트, 그리고 SSR 서버와도 별도의 환경이라... 뭔가 또 다른 서버가 존재한다는 걸까요? 그리고 어떻게 미리 렌더링하고, 사용자와 상호작용을 할까요?
자, 동작을 이해하기 위해서, 어떻게 렌더링이 되는지를 파악해 보겠습니다.
잠깐!
Next.js App Router도 RSC를 이해하는데 매우 중요한 역할을 합니다. 리액트 팀이 만들어가는 비전은 Next.js 팀과 긴밀한 협력으로 구현되고 있습니다. 리액트 공식 문서에 따르면 다음과 같은 이야기를 합니다.
Next.js’s App Router is a redesign of the Next.js APIs aiming to fulfill the React team’s full-stack architecture vision.
Next.js’s App Router bundler fully implements the official React Server Components specification. This lets you mix build-time, server-only, and interactive components in a single React tree.
(Next.js’s App Router는 React 팀의 풀스택 아키텍처 비전을 구현하기 위해 재설계된 Next.js API입니다.
Next.js의 App Router 번들러는 공식 React Server Components 명세 전체를 구현했습니다. 이를 통해 빌드 시간, 서버 전용, 대화형 컴포넌트를 하나의 React 트리에서 혼합할 수 있습니다.)
리액트 18까지의 기본 컴포넌트가 클라이언트 컴포넌트로서 역할을 하고, RSC가 서버의 역할을 일부 대체하면서 풀스택을 향해 나아가는 걸로 보입니다. 그리고 code-splitting, routing, data fetching, 그리고 HTML 생성을 지원하는 Next.js 앱 라우터와의 협력을 통해 완성도를 높이고 있죠. 참고로, Next.js 13의 앱 라우터부터 RSC가 지원되고 특별히 지정("use client") 하지 않으면, 서버 컴포넌트가 기본 컴포넌트가 됩니다.
그리고 서버 컴포넌트라는 말 때문에, 리액트 서버가 따로 존재하는가에 대한 의문이 들 수 있을 거예요. 그 답은 리액트 개발팀 단의 게시글에서 힌트를 얻을 수 있습니다.

The RSC Server layer might remind you of Remix loaders, Astro templates, build-time scripts, and other code that runs ahead-of-time — but in the form of React components.
One way to think about it is that in RSC, "Server" and "Client" doesn't directly correspond to a physical server and client. You can think of them more as "React Server" and "React Client". Props always flow from React Server to React Client, and there is a serialization boundary between them. React Server typically runs either at the build time (default), or on an actual server.
(RSC 서버 레이어는 Remix 로더, Astro 템플릿, 빌드 타임 스크립트 및 기타 미리 실행되는 코드와 비슷할 수 있으며, React 컴포넌트 형태로 제공됩니다.
한 가지 생각해 볼 수 있는 방식은 RSC에서 "서버"와 "클라이언트"는 물리적인 서버와 클라이언트에 직접적으로 대응되지 않는다는 것입니다. 대신에 "React 서버"와 "React 클라이언트"로 생각할 수 있습니다. Props는 항상 React 서버에서 React 클라이언트로 흐르며, 이들 사이에는 직렬화 경계가 존재합니다. React 서버는 일반적으로 빌드 타임(기본 값)이나 실제 서버에서 실행됩니다.)
이 글에서 알 수 있는 건, RSC는 실제 서버가 아닌 서버에서만 동작하는 컴포넌트 형태의 코드이며, 빌드 타임이나 실제 서버에서만 동작한다는 점입니다. 다시 말해, 리액트 서버는 서버 측에서 실행되는 코드임을 알 수 있습니다.
RSC 요청 흐름
이 글의 꽃이라 불릴 수 있는 요청 흐름입니다. 아래 도표의 순서대로 한 번 따라가 본 뒤, 요청 흐름 설명을 읽어보면 이해하기 훨씬 쉽습니다.

사용자가 방문해서 페이지가 준비되기까지 하나하나 따라가 보겠습니다.
- 흐름: User => RSC
사용자가 접속하면, 브라우저는 서버에 페이지를 요청합니다. SSR 서버는 RSC Payload를 리액트 서버에 요청합니다.

2. 흐름: RSC => SSR
서버 컴포넌트는 기본 HTML 태그로, 클라이언트 컴포넌트는 앞으로 위치할 자리로 표시되어 렌더링 됩니다. 그 결과물인 클라이언트 JSX 트리는 직렬화되어 청크 단위로 만들어지며, 만들어지는 족족 SSR 서버로 보내집니다.
3. 흐름: SSR => Browser
SSR 서버는 클라이언트 JSX 트리를 바탕으로 초기 HTML을 생성하는 족족 브라우저에 보냅니다. 서버 API(renderToPipeableStream)를 통해 HTML을 클라이언트로 스트리밍 됩니다. 여기에는 HTML 뿐만 아니라 위치를 지정해 주는 <script> 태그도 포함됩니다.

4. 흐름: Browser => SSR
브라우저는 초기 HTML을 점진적으로 렌더링 합니다. Suspense를 활용하면, 전체 HTML이 도착하지 않더라도 독립적으로 렌더링 할 수 있습니다. 그리고 클라이언트 컴포넌트 실행에 관련된 JS 번들을 SSR 서버에 요청합니다.

5. 흐름: SSR => User
요청한 JS 번들이 서버로부터 도착하는 족족, (트리 탐색을 시작할 때 바로) 하이드레이션이 실행됩니다. Suspense로 지연시켰던 컴포넌트가 로드되지 않더라도 독립적으로 진행됩니다. 하이드레이션이 완료된 컴포넌트들은 상호작용이 가능하게 됩니다.
이상으로 여기저기 흩어져있는 RSC에 대한 자료를 정리해 보았습니다. RSC를 이해하는데 도움이 되었기를 바랍니다.
출처
Kent C.Dodds - RSC 요청 흐름
Why do Client Components get SSR'd to HTML
'React.js' 카테고리의 다른 글
React 렌더링 (feat. Reconciliation & Fiber 동작) (0) | 2024.06.30 |
---|---|
RSC가 비동기 함수를 관리하는 방법 (feat. Observer Pattern) (0) | 2024.06.18 |
Apollo Client Cache로 전역 상태 관리하기 (0) | 2024.06.06 |