카테고리 없음

리액트 서버 컴포넌트는 어떻게 작동하는가: 심층 가이드

Ahyeon, Jung 2024. 8. 20. 01:15

원문: How React server components work: an in-depth guide (plasmic.app)

 

How React server components work: an in-depth guide

A deep dive exploration of React server components under the hood.

www.plasmic.app


리액트 서버 컴포넌트(React Server Component, RSC)는 앞으로 페이지 로딩 성능, 번들 사이즈, 우리가 React 애플리케이션을 작성하는 방식에 거대한 영향을 미칠 매우 흥미로운 새로운 기능입니다. 우리 Plasmic은 React용 visual builder를 만들고 React의 성능에 많은 관심을 가지고 있습니다. 많은 고객들이 Plasmic을 사용하여 성능이 중요한 마케팅 및 전자 상거래 사이트를 구축하고 있습니다. 따라서, RSC가 아직 여전히 React 18에서 초기 실험적인 기능이긴 하지만, 그 작동 방식을 깊이 파헤치고 있습니다. 이번 블로그 포스팅을 통해서 우리가 배운 것들을 공유할 수 있어서 기쁩니다.

 

간단한 목차입니다.

리액트 서버 컴포넌트란 무엇일까?

- 이건 서버 사이드 렌더링이 아닌가?

- 우리는 이것이 왜 필요한가?

전반적인 특징

서버-클라이언트 컴포넌트의 구분

RSC 렌더의 생명주기

- 서버가 렌더 요청을 받는다.

- 서버가 루트 컴포넌트 요소를 JSON으로 직렬화한다.

- 브라우저가 React 트리를 재구성한다.

- 이것은 Suspense와 어떻게 작동하는가?

RSC Wire Format

- RSC 포맷의 사용

- 왜 기본 HTML 결과가 아닌가요?

- 이것은 클라이언트 컴포넌트에서 데이터를 가져오는 것보다 나은가?

- 그러나,, 서버 사이드 렌더링은 어떤가?

당신의 서버 컴포넌트가 렌더링하는 내용을 업데이트 하기

왜 RSC를 위해 meta-프레임워크를 사용이 필요할까?

RSC는 준비되었는가?

리액트 서버 컴포넌트에서의 Plasmic


리액트 서버 컴포넌트란 무엇일까?

리액트 서버 컴포넌트는 당신의 React 애플리케이션을 랜더링할 때서버와 클라이언트(브라우저)가 결합하는 것을  허용한다. 그 아래 더 많은 리액트 컴포넌트를 렌더링하는 다른 컴포넌트들로 보통 구성된 당신의 페이지를 렌더링하는 전형적인 리액트 요소 트리를 고려해보자. RSC는 이 트리에서 몇개의 컴포넌트는 서버에서, 몇개의 컴포넌트는 브라우저에서 렌더링하는 것을 가능하게 만든다.

 

여기 그들의 궁극의 목표를 보여주는 리액트 팀의 빠른 설명이 있다. 오렌지색 컴포넌트는 서버에서 렌더링되고, 파란색 컴포넌트들은 클라이언트에서 렌더링되는 리액트 트리다.

RSC은 SSR이 아닐까?

리액트 서버 컴포넌트는 서버 사이드 렌더링(SSR)이 아니다! 이것은 둘 다 이름에 서버가 들어가 있고 둘 다 서버에서 작동하기 때문에 조금 혼란스럽다. 그러나 돌다 완전히 분리되어있고 독립적인 특징이라고 보는 것이 훨씬 쉽다. RSC를 사용하는 것은 SSR의 사용을 요구하지 않고, 반대도 마찬가지다! SSR은 HTML 그 자체에 리액트 트리를 렌더링한 것을 환경에서 시뮬레이션하는 것이다. SSR은 서버 컴포넌트와 클라이언트를 구분하지 않고, 같은 방식으로 렌더링한다!

 

이것은 SSR과 RSC 둘 다 결합한다. 그래서 우리는 서버 컴포넌트를 사용하는 서버 사이드 렌더링을 할 수 있고 그들을 브라우저에서 적절히 결합(hydrate)할 수 있다. 앞으로, 우린 그들이 어떻게 함께 작동하는 가에 대해 말할 것이다.

 

그러나 일단, SSR을 무시하고, RSC에 온전히 집중해보자.

 

왜 우리는 RSC를 원할까?

RSC 전에, 모든 리액트 컴포넌트는 브라우저에서 모두 작동하는 클라이언트 컴포넌트였습니다. 브라우저가 리액트 페이지를 방문하면, 필요한 리액트 컴포넌트의 코드를 다운로드하고, 리액트 element tree를 구성하고, 이를 DOM에 렌더링했다.(만약 SSR을 사용한다면, DOM을 hydrate했다.). 당신의 리액트 애플리케이션을 상호작용이 가능하게 만들어주기 때문에 브라우저는 이를 위한 좋은 장소였다. 당신은 이벤트 핸들러를 설치하고, 상태를 추적하며, 이벤트에 응답하여 React 트리를 변경하고, DOM을 효율적으로 업데이트할 수 있었다. 그렇다면, 우리는 왜 서버에서 어것을 렌더링하려고 할까요?

 

브라우저를 넘어 서버에서 렌더링하는 것에는 다음의 장점이 있습니다:

  • 서버는 데이터베이스, GraphQL 엔드포인트, 파일 시스템 등의 데이터 소스에 더 직접적인 접근할 한다. 서버는 공개된 API 엔드포인트를 통하지 않고도 데이터를 직접적으로 가져올 수 있다. 그래서 서버를, 브라우저보다 더 빠른 데이터 패칭을 할 수 있다. 
  • 서버는 자바스크립트 번들을 통해 사용하는 코드를 다 다운로드 받아야하는 브라우저와 달리, 그들이 사용하는 의존성을 매번 다운로드할 필요가 없다. 그래서 마크다운을 HTML로 렌더링하는 npm 패키지와 같이, 무거운 코드 모듈 사용을 값싸게 진행할 수 있다.

간단하게 말해서, 리액트 서버 컴포넌트는 서버와 브라우저가 그들이 각각 잘하는 것을 할 수 있게 한다. 서버 컴포넌트는 데이터 패칭과 내용 렌더링에 집중하고, 클라이언트 컴포넌트는 stateful한 상호작용에 집중할 수 있어서 결과적으로 빠른 페이지 로드, 가벼운 자바스크립트 번들 사이즈, 그리고 더 좋은 UX를 보여줄 수 있다.

전반적인 특징

RSC이 어떻게 작동하는지 직관적으로 알아보자.

 

내 아이는 데코가 된 컵케익을 사랑하지만, 그들은 만들 수는 없다. 아이들에게 컵 케이크를 만들고 꾸미라고 요청한다면 그건 (사랑스러운) 악몽이 될 수 있다. 나는 밀가루와 설탕들, 버터 등을 다뤄야하고, 오븐에 접근할 수 있게하고, 많은 설명서들을 읽어야하고 온종일을 써야하낟. 그렇지만, 나는 베이킹 부분을 좀 더 빠르게 할 수 있다. 내가 초반에 컵케이크를 굽거나 frosting을 만들어서 초반을 도와서 아이들이 다룰 수 있게 한다면, 그들은 재미있는 장식을 빨리 할 수 있다. 더 좋은건, 아이들이 오븐을 사용하는 것을 걱정할 필요가없다. 예!

리액트 서버 컴포넌트는 이런 작업 분담을 가능하게 해준다. 서버가 더 잘할 수 있는 작업들을  미리 진행하고, 브라우저가 남은 것들을 끝내도록 한다. 이렇게 함으로써, 서버는 덜 간섭할 수 있다. 마치 모든 밀가루와 오븐 사용 대신에, 12개의 컵케이크만 운반하는 것이 훨씬 효율적인 것처럼말이다.

 

몇개는 서버에서, 몇개는 클라이언트에서 렌더링되는 컴포넌트를 가진 페이지의 리액트 트리를 고려해보자. 여기 전반적인 전략에 관한 간단한 예시가 있다: 서버는 리액트 컴포넌트를 보통 div나 p와 같은 HTML 태그로 바꾸면서 서버 컴포넌트만 렌더링할 수 있다. 그러나 브라우저에서 렌더링되는 클라이언트 컴포넌트를 마주치면, 그것은 원래의 클라이언트 컴포넌트와 props 대신에 구멍으로 채우는 플레이스 홀더를 결과물로 기록한다. 그러면, 브라우저는 클라이언트 컴포넌트로 이 구멍을 채워넣는다. 이게 끝이다. 

 

물론 진짜로 작동하는 방식은 아니고, 건너 뛴 세부사항들을 곧 얘기할 것이다. 그러나 이 유용한 대략적인 개요를 기억해라!

 

서버 컴포넌트와 클라이언트 컴포넌트의 구분

일단 먼저, 서버 컴포넌트란 무엇일까? 어떻게 우리는 무엇이 서버를 위한 컴포넌트고, 클라이언트 컴포넌트인건지 말할 수 있을까? 

 

리액트 팀은 .server.jsx로 파일이 끝나면 서버 컴포넌트를 포함하고, .client.jsx로 끝나면 클라이언트 컴포넌트를 포함하도록 파일 확장자를 통해 정의했다. 만약 확장자가 둘 다 아니라면, 서버 컴포넌트와 클라이언트 둘 다 사용하고 있는 컴포넌트라고 정의했다.

 

이 정의는 사람과 번들러 둘 다 서버 컴포넌트와 클라이언트 컴포넌트를 분간할 수 있어서 실용적이다. 번들러를 위해 특별히, 파일 이름을 조사하여 클라이언트 컴포넌트를 다룰 수 있다. 곧 볼 것처럼, 번들러는 서버 컴포넌트가 작동하는데 있어 중요한 역할을 한다.

 

서버 컴포넌트는 서버에서 작동하고 클라이언트는 클라이언트에서 작동하기 때문에, 각자가 할 수 있는 것에 많은 제약이 있다. 그러나 명심해야할 가장 중요한 것은 클라이언트 컴포넌트는 서버 컴포넌트를 import할 수 없다는 점이다! 서버 컴포넌트는 브라우저로 데려올 수 없고, 브라우저에서 작동하지 않는 코드를 가질 수 있기 때문이다. 만약 클라이언트 컴포넌트가 서버 컴포넌트에 의존하고 있다면, 우리는 결국 엮인 의존성들을 브라우저 번들에 포함하게 될 것이다.

 

마지막 말은 이해하기 어려울 수 있는데, 다음과 같은 클라이언트 컴포넌트가 불가능하다는 뜻이다.

// ClientComponent.client.jsx
// NOT OK:
import ServerComponent from './ServerComponent.server'
export default function ClientComponent() {
  return (
    <div>
      <ServerComponent />
    </div>
  )

 

만약 클라이언트 컴포넌트가 서버 컴포넌트를 import할 수 없고 서버 컴포넌트를 인스턴스화할 수 없다면, 어떻게 서버와 클라이언트 컴포넌트가 서로 섞여 있는 React 트리를 생성할 수 있을까요? 어떻게 우리는 클라이언트 컴포넌트 아래에 서버 컴포넌트를 가질 수 있을까요?

클라이언트 컴포넌트에서 서버 컴포넌트를 import하고 렌더할 수는 없지만, 우리는 composition을 활용할 수 있다. 즉, 클라이언트 컴포넌트는 ReactNode를 props로 받을 수 있고, 이 ReactNodes는 서버에서 렌더링된 결과물일 수 있다. 다음 예시를 보자:

// ClientComponent.client.jsx
export default function ClientComponent({ children }) {
  return (
    <div>
      <h1>Hello from client land</h1>
      {children}
    </div>
  )
}

// ServerComponent.server.jsx
export default function ServerComponent() {
  return <span>Hello from server land</span>
}

// OuterServerComponent.server.jsx
// OuterServerComponent can instantiate both client and server
// components, and we are passing in a <ServerComponent/> as
// the children prop to the ClientComponent.
import ClientComponent from './ClientComponent.client'
import ServerComponent from './ServerComponent.server'
export default function OuterServerComponent() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  )
}

 

이 제한은 RSC의 장점을 활용하기 위해 컴포넌트를 구성하는데 상당한 영향을 끼칠 것이다.

RSC 렌더의 생명주기

리액트 서버 컴포넌트를 렌더하려고 할 때 실제로 일어나는 것들을 자세히 알아보자. 서버 컴포넌트를 사용하기 위해 여기의 모든 것들을 알 필요는 없지만, 작동하는 방식에 몇가지 직관을 얻을 수 있을것이다.

1. 서버가 렌더 요청을 받는다