JavaScript/React

Next.js와 RSC(React Server Component)를 공부하며

kjmin 2024. 9. 22. 18:51

Next.js를 맨 처음 접한게 대학원생 시절 v3~v4 쯤 되는데, 벌써 v14까지 나와있고 v15 릴리즈를 코앞에 두고 있다...-_-
그동안 React를 CSR 위주로만 개발하다가 한참 outdated되어 있는 나의 Next.js 지식을 업데이트 하기 위해 여러가지 찾아보는 중
올해부터 Next.js를 다시 사용하면서 막연하게 사용법만 숙지한채로 쓰고있는 Server component의 동작 원리를 제대로 알고 쓰면 좋을 것 같아서 조사를 시작했다

What is RSC(React Server Component)?

RSC는 서버사이드 렌더링을 주 목적으로 설계된 React 컴포넌트의 한 유형으로, React v18 및 Next.js v13부터 RSC가 도입되기 시작했다.
RSC가 등장하기 이전의 기본적으로 쓰던 컴포넌트는 자연스레 RCC(React Client Component)라는 명칭이 붙었고, RCC와의 차이점은 "서버사이드에서 동작해야 할 로직(data fetching, DB access, FS access 등등)을 컴포넌트 내부에서 직접 실행할 수 있다는 것이다.

Why RSC?

앞서 설명했듯이, RSC의 가장 큰 특징은 서버사이드에서 처리되어야 할 로직을 실행할 수 있다는 점이다. 기존의 SSR(Server Side Rendering) 방식은 서버사이드에서 컴포넌트를 먼저 HTML로 렌더링 하고 컴포넌트에서 필요한 모든 JavaScript를 번들링하여 내보내고 브라우저에서는 렌더링 결과물과 JavaScript 로직을 연결하는 hydration이라는 작업을 하는데, 이 과정에서 클라이언트 측에서 소모하는 처리시간 및 JavaScript 번들링 용량 증가를 완화하기 위해 RSC가 등장하게 되었다.
RSC 장점:

  1. DB 접근, 파일시스템 접근 등 시스템 호출을 컴포넌트 수준에서 직접 호출할 수 있으며, 모든 컴포넌트 렌더링이 서버 사이드에서 처리된다.
  2. 클라이언트의 hydration 및 JS 번들링 부담이 줄어든다.

RSC 단점:

  1. Web API 기반으로 동작하는 프로퍼티를 props로 전달할 수 없다. (i.e., onClick)
  2. JSON 직렬화가 불가능한 데이터를 props로 전달할 수 없다. (i.e., Function)

단점에 해당하는 부분이 Frontend 개발에서 적지 않은 비중을 차지하기 때문에, RSC를 도입할 경우 컴포넌트 디자인 패턴이 다소 복잡해질 것으로 예상한다.
단점 2번의 경우에는 상황에 따라 다르게 동작하는데, props로 함수를 전달하는 상황을 가정하면 다음과 같이 동작한다.
(1) RSC -> RSC로 함수 전달: 동작함
(2) RSC -> RCC로 함수 전달: 오류 발생
(3) RCC -> RCC로 함수 전달: 동작함
다시 말하면, RSC끼리 JSON 직렬화가 불가능한 props 전달은 서버사이드에서 렌더링을 마치고 직렬화를 시작하므로 문제가 발생하지 않는다.

RSC의 특징을 기준으로, 기존의 다른 개발 방식과 비교하자면 다음의 표와 같다.
Traditional MVC/SSR: MVC 패턴 및 HTML/CSS/JS를 페이지별로 개발하던 전통적인 방식의 SSR
React/CSR: 서버사이드 JS 런타임 없이 정적인 HTML/JS 빌드 결과물만 가지고 동작하는 환경의 React 애플리케이션
React/RCC: SSR 환경에서 동작하는 React Application
React/RSC: SSR 환경 + RSC로 동작하는 React Application

Server Side Rendering X X O(getServerSideProps) O
DB/FS Access O X O O
JS Hydration X X O X

(비교할 메트릭이 많겠지만, RSC의 대표적인 특징과 비교만을 위한 표이므로 많이 생략했음)
Next.js의 SSR은 Hydration이라는 과정 때문에 사실은 전통적인 방식의 SSR과 완전히 같은 의미가 아니라는 것을 알 수 있다.
CSR와 SSR 이란 용어 자체가 SPA라는 개념이 등장하면서부터 본격적으로 쓰이기 시작했으니, 이 차이를 알아나가는 것이 흥미로운 포인트이다

How does RSC work?

기본적으로 React의 렌더링 트리에서는 RSC와 SSR을 구별하지 않기 때문에, RSC를 사용한다고 해서 특정 페이지의 콘텐츠 전체를 RSC만으로 채워야 할 필요가 없고 일부는 서버에서 미리 렌더링하고 일부는 브라우저에서 렌더링 하는 방식으로 동작한다.
React를 이용해서 현재 구현중인 페이지가 있다고 할 때, 일정한 패턴 없이 RSC와 RCC가 여러 계층에 분포되어 있는 경우의 DOM tree를 표현하면 다음과 같을 것이다.

SSR 서버는 위 DOM tree를 JSON으로 직렬화하여 클라이언트에게 보내야 하는데, 이 때 tree를 변환하는 과정에서 RSC는 HTML로, RCC는 placeholder로 대체된다.
위 과정을 Next.js에서 간단한 코드를 실행하고 직접 확인해보고 싶어서 create-next-app으로 프로젝트 하나 만들었다.
RSC와 RCC로 단순한 텍스트만을 렌더링하는 컴포넌트 하나씩 만들어서 두 컴포넌트를 그리는 /practice라는 페이지를 만들었는데, Next.js에서는 /practice 페이지로 이동할 때 해당 페이지의 컴포넌트 트리에 대한 정보를 통신하는데 그 내용은 다음과 같다.

(dev start했을 때와 production start했을 때의 데이터가 각각 다르고, Next.js에서 정의한 데이터 구조에 대한 지식이 없으므로 정밀한 분석은 생략하겠음)
대충 직관적으로 보이는 핵심 내용만 보자면, 스트림의 3번에 RCC.tsx의 CSR을 위한 placeholder로 보이는 데이터가 있고, RCC와 형제였던 RSC는 4번에 렌더링 된 결과물에 대한 정보가 그대로 들어갔음을 확인할 수 있다.

Conclusion

RSC가 목표하는 이점에 비해 React의 구조를 너무 복잡하게 만들고 React 개발자가 정량적으로 판단하기 어려운 trade-off 지점이 많아지는 것이 아닌가 하는 의구심도 들었지만, 아무튼 현재의 Next.js의 SSR 생태계에서 애플리케이션의 성능을 최적화 할 요소 중 하나임은 틀림 없으므로 계속해서 관심가져야 할 기술인 것 같다.
React를 애용하는 빅테크에서 RSC 도입 후의 성능 변화를 모니터링 하고, 쌓인 데이터를 기반으로 다양한 관점이 나올 것으로 기대한다.

References

  1. https://react.dev/reference/rsc/server-components
  2. https://nextjs.org/docs/app/building-your-application/rendering/server-components
  3. https://dev.to/xakrume/react-server-components-deep-dive-29af
  4. https://github.com/facebook/react/blob/a1c62b8a7635c0bc51e477ba5437df9be5a9e64f/packages/react-client/src/ReactFlightClient.js#L911

'JavaScript > React' 카테고리의 다른 글

vscode problems with Yarn Berry(PnP) & Workspace  (0) 2024.09.14