들어가는 말

Next 13 업데이트에 app directory가 되면서, app directory 내부 모든 컴포넌트는 기본적으로 서버 컴포넌트로 동작이 됩니다. 그러나 이를 클라이언트 컴포넌트로 설정하고자 한다면 파일 최상단에 use client를 선언하면 됩니다.

그런데 어떤 상황에 서버, 클라이언트 컴포넌트를 쓰면 좋을까요? 두 컴포넌트의 목적과 차이점 등을 살펴보고자 합니다.


1


RSC가 뭐지요?

RSC는 React Sever Component의 약자로 말 그래도 서버 컴포넌트입니다. 이는 서버에서 실행되는 컴포넌트 로 이해할 수 있습니다.

그러나 서버에서 어떻게 컴포넌가 렌더링되고 그려지는지는 아직 감이 잘 잡히지 않습니다.


2



컴포넌트란 무엇이지?(with 클라이언트 컴포넌트의 렌더링 과정)

서버 클라이언트 컴포넌트를 나누기 전, 일반적으로 우리가 사용해왔던 컴포넌트에 대해서 다시 살펴보고자 합니다.

컴포넌트(Component)란 데이터를 인자로 받아 그것을 가지고 jsx를 return 합니다.


function HelloWrold(user) {
  const [name, setName] = useState(user);

  return <H1>Welcome {name}</H1>;
}


위와 같은 JS 함수를 우리는 컴포넌트라고 합니다.

인자로 받는 user라는 데이터는 컴포넌트의 props일 것입니다. 그리고 컴포넌트 내부에서 활용하는 데이터는 state로 관리합니다.

그런데 이런 JS 함수가 렌더링 된다는 것은 무엇일까요?

자, 다시 돌아가서 컴포넌트가 jsx를 리턴한다는 것은 모두 이해하셨을 것입니다.

그런데 이 과정에서 Babel이라는 것이 등장합니다. Babel을 만나 jsx는 React element로 해석이 됩니다.

React element는 DOM관련 정보를 담고 있는 js 객체입니다. 다시 말해 React elementDOM에 표현하기 위해 필요한 정보를 담고 있는 js 객체입니다.

React element가 확장되면, fiber(node)가 됩니다. 이후 fiber는 Virtual DOM(이하 V-DOM)의 node가 됩니다. (fiber가 모여 V-DOM을 그린다. 따라서 V-DOM은 fiber로 이루어진 tree로 볼 수 있습니다.)

이런 V-DOM은 실제 DOM에 반영되는 과정을 거칩니다.

이런 일련의 과정을 가지고 우리는 렌더링이라고 부릅니다!


3


우리가 지금까지 알던 렌더링 방식은 앞에서 이야기한 클라이언트 컴포넌트의 렌더링 방식입니다.
그러나 우리가 궁금한 건 “서버”컴포넌트고, 서버 컴포넌트는 어떻게 렌더링이 되는 걸까요?


4


서버 컴포넌트란 무엇이지?(with 서버 컴포넌트의 렌더링 과정)

어떻게 렌더링 되고 무엇인지를 알려면 왜 만들어졌는지를 알고 접근하는 것이 더 이해가 빠를 것입니다.

Dan Abramov가 말하길 “본래 리액트 컴포넌트는 하나였습니다.(클라이언트 컴포넌트). 그러나 이것을 확장하여 서버 컴포넌트라는 개념을 창조했습니다.

그럼 리액트에서는 컴포넌트라는 개념을 확장하여 서버 컴포넌트를 만들게 되었을까요?

  1. 서버 컴포넌트는 서버가 할 수 있는 모든 것을 할 수 있습니다.


5


상품의 상세 페이지에 접근을 하는데 클라이언트 컴포넌트 방식을 사용했다면, 상세 페이지에 들어갔을 때, 필요한 데이터를 서버에 요청해서 받아오게 될 것입니다.

반면 서버 컴포넌트는 직접 서버에서 접근하여 필요한 값을 가져올 수 있을 것입니다. 이런 방식은 클라이언트 단에서의 추가적인 데이터 요청을 진행하지 않기에 속도 면에서도, 초기 데이터를 호출하는 코드 생략 등이 가능합니다.


  1. 서버 컴포넌트의 코드는 클라이언트로 전달되지 않습니다.

서버 컴포넌트의 코드가 클라이언트로 전달되지 않으니, 민감한 정보도 편리하게 다룰 수 있게 됩니다. 이로 신뢰도가 증가하는 컴포넌트를 개발할 수 있습니다.

RSC는 서버에서 이미 렌더링 된 다음 클라이언트에게 직렬화(serialize) 된 형태로 전달되기 때문에 클라이언트 사이드에서 추가적인 로드가 필요 없게 됩니다!

Speed, Trust 두 가지의 큰 목적을 가지고 서버 컴포넌트가 우리에게 등장 하게 되었습니다.


6


그러면 서버 컴포넌트는 어떻게 렌더링이 되는 것일까요?

Next란 React api를 가지고 렌더링을 합니다.


렌더링 워크청크로 나눠집니다. 나누는 것에 대한 기준으로는

  1. 라우트 세그먼트 (route segments)
  2. 서스펜스 바운더리 (Suspense Boundaries)

위 두 가지를 기준으로 렌더링 워크 됩니다. 이는 다시 말해 렌더링 워크는 청크로 나눠집니다.

그러면 이 나눠진 청크를 어떻게 쓰는지 고민을 해봐야 합니다.

이때 리액트가 나타나게 됩니다.


[서버]

1. 리액트가 서버 컴포넌트를 렌더

서버 컴포넌트를 렌더 한다란 말은 즉, RSC payload라는 이름으로 불리는 스페셜 데이터 포멧으로 리액트는 서버 컴포넌트를 렌더링 한다는 뜻입니다.

따라서, 리액트는 서버 컴포넌트를 RSC payload로 변환하는 것입니다.


그럼 추가적으로 드는 생각이 있습니다.

  • RSC payload가 뭘까?


리액트 서버 컴포넌트 트리를 그리기 위한 바이너리입니다.

  • 서버 컴포넌트의 렌더링 결과물이 존재
  • placeHolder 존재 ⇒ 빈자리 표시(빈자리 === 클라이언트 컴포넌트, 클라이언트 컴포넌트가 어디 위치에서 렌더 될 것인지 나타내는 것.)
  • 서버 컴포넌트가 클라이언트 컴포넌트에게 전달하는 props 존재


요약하자면 서버 컴포넌트는 리액트가 렌더 한다라는 것입니다.

⇒ “렌더한다”라는 것은 결국 RSC를 RSC payload로 만드는 과정입니다.


2. Next가 RSC payload + 클라이언트 JS instruction 활용해 HTML을 렌더

이 과정 또한 서버에서 일어납니다.

그다음은 클라이언트가 넘겨받게 됩니다.


[클라이언트]

1. 서버에서 전달받은 HTML을 즉시 보여줌

이것이 preview에 보이는 요소일 것입니다. 이니셜 페이지로 처음 새로고침을 눌렀을 때 보입니다.


2. RSC payload 활용

reconsile(재조정)을 위해서 RSC payload기능을 활용합니다.

RSC payload를 활용하여, 비어있던 클라이언트 컴포넌트 부분을 채워 넣습니다. 이렇게 온전하게 구성된 트리가 바로 리액트 컴포넌트 트리가 됩니다. 그리고 이것은 V-Dom이 되고 V-Dom이 실제 Dom을 업데이트하기도 합니다.


3. **Hydrate (수화)**

이는 Next를 사용하셨으면 종종 들어보셨을 것입니다.

바로 인터렉션을 가능하게 하는 부분이지요. JS 인스트럭션을 가져와 단순한 HTML에 다양한 이벤트나 setState함수 등을 붙입니다. 그저 태그만 있던 것들에 다양한 인터렉션이 가능하게 해줍니다.


요약

[서버]

  1. 리액트가 서버 컴포넌트는 렌더: 서버 컴포넌트를 RSC 페이로드로 변환하는 것.
  2. Next가 HTML을 렌더: RSC payload + 클라이언트 JS instruction 활용해 html을 렌더링

[클라이언트]

  1. 서버에서 넘겨받은 HTML 보여줌
  2. RSC payload 활용하여 클라이언트 컴포넌트 부분 채움
  3. Hydrate

위 과정을 거치며 서버 컴포넌트는 렌더링 됩니다.


7


Next.js 공식 문서에는 RSC와 RCC의 명확하게 구분이 되어있습니다.


8


자 그럼 RSC와 RCC의 차이점과 방식에 대해서는 조금씩 이해가 가기 시작하기 시작합니다.

그런데 저는 여기까지 공부를 해오면서 다시금 궁금해지는 한 부분이 있었습니다. 그렇다면 SSR은 도대체 뭐지? 라는 생각이 들기 시작했습니다.

SSR은 미리 HTML을 서버에서 그려서 클라이언트에 내려주고, 그것으로 인해 필요한 키워드가 이미 HTML에 담겨와서 SEO도 가능한 것이고, 사용자가 느끼기에 페이지가 빨리 불러온다고 느껴 사용자 경험도 나아지고… 그런데 이런 건 RSC에서도 동일하게 해주는거 아니야?라는 물음표가 마구 들기시작했습니다.


SSR은 뭐죠?(SSR VS RSC)

SSRRSC는 “서버에서 렌더링이 된다”라는 점만 같고 원하는 것, 진행방식, 목적은 모두 다릅니다.

[SSR]

  • 목적: 인터랙티브 하지 않은 정적인 HTML을 최대한 빠르게 브라우저에게 전달하여 초기 페이지가 뜨는 속도를 향상시킨다.
  • SSR에서의 렌더링은 서버에서 React 컴포넌트를 사용하여 완전한 HTML 페이지를 생성하는 과정
  • 클라이언트가 HTML을 받으면 React는 Hydration과정 진행
  • 초기 페이지 로딩 시간 단축, SEO 향상에 초점

[RSC]

  • 목적: 서버가 하는 작업들을 동일하게 할 수 있는 컴포넌트를 구현
  • RSC에서의 렌더링은 서버 측에서 React 컴포넌트의 로직을 실행하는 과정
  • 최종 목적은 HTML 생성이 아닌 컴포넌트 로직 처리하고 결과 생성
  • 서버에서 직접 데이터를 가져오거나 클라이언트에 전송되는 데이터와 JS 번들 사이즈 최소화에 초점


우리는 서버라는 공통점을 찾기보다는 Rendering, Component라는 차이점과 각자의 목적에 집중을 해보면 이해가 더 빠를 수 있을 것입니다.

또한, 차이점으로는

  • RSC의 코드는 클라이언트에게 전달되지 않음

    • SSR의 코드는 JS 번들에 포함되어 모두 클라이언트로 전송
  • RSC의 컴포넌트는 페이지 레벨과 상관없이 모두 서버에 접근 가능

따라서, RSC는 SSR의 대체가 아닌 보완 추가적인 요소로 사용 가능합니다.

  • SSR로 초기 HTML 페이지를 빠르게 보여줌
  • RSC로 클라이언트에게 불필요한 JS 코드를 뺀다(서버 컴포넌트를 통해) 이것으로 JS 번들 사이즈를 줄이기 가능

두 방식을 적절하게 사용하면 사용자에게 기존보다 훨씬 빠르게 인터랙티브한 페이지를 제공할 수 있을 것입니다.


마무리 말

Next로 개발을 진행하며, 이름은 알지만 실제적으로 어떻게 동작하는지 무엇을 위해 나왔는지는 이해하지 못하는 것들이 꽤나 많았습니다. SSR, RSC 등을 렌더링 과정부터 살펴보며 이해를 해보니 SSR과 RSC가 무엇이 다른지, 왜 쓰는지 인지할 수 있었습니다.

무조건적으로 좋다기보다는 해당 기술이 필요할 때 사용한다는 개념으로 적재적소에 잘 활용하는 법을 익혀야겠습니다.


참고 자료

https://nextjs.org/docs/app/building-your-application/rendering/server-components#how-are-server-components-rendered

https://tech.kakaopay.com/post/react-server-components/#리액트-서버-컴포넌트rsc와-서버-사이드-렌더링ssr

https://yceffort.kr/2022/01/how-react-server-components-work#개괄

https://www.youtube.com/watch?v=XdiMjKSCOfc