본문 바로가기

FrontEnd

[3분 리액트] React18 Suspense의 Streaming SSR에 대해 알아보자

반응형

원문 : https://blog.saeloun.com/2022/01/20/new-suspense-ssr-architecture-in-react-18

 

Deep dive into the new Suspense Server-side Rendering ( SSR ) architecture in React 18

Ruby on Rails and ReactJS consulting company. We also build mobile applications using React Native

blog.saeloun.com

TLDR

서스펜스는 SSR의 다음과 같은 문제를 해결합니다.

  • 페이지 상호작용을 위해 모든 것들을 수화해야 합니다. 페이지는 한번에 인터랙티브하게 됩니다.
  • 수화를 시작하기 전에(페이지를 인터랙티브 하게 만는 작업을 시작하기 전에) 모든 html / js를 로딩해야 합니다.
  • 무언가 사용자에게 보여주려면 필요한 데이터가 이미 먼저 다 있어야 합니다.

React 18은 새로운 Suspense SSR 아키텍처를 제공합니다.
새로운 아키텍처를 이해하려면 클라이언트 측 렌더링, 서버 측 렌더링, 수화(hydration) 등과 같은 기본 개념에 익숙해야 합니다.

SSR 동작방식

SSR에서는 데이터를 가져오고 서버의 React 컴포넌트에서 HTML을 생성합니다.
그런 다음 HTML이 클라이언트로 전송됩니다.
  1. 서버의 전체 애플리케이션에 대한 데이터를 가져옵니다.
  2. HTML은 서버의 React 컴포넌트에서 전체 애플리케이션을 대상으로 생성된 다음 클라이언트로 전송됩니다.
  3. 클라이언트( 브라우저 )에서 전체 애플리케이션에 대한 JavaScript 코드가 로드됩니다.
  4. 그런 다음 JavaScript 논리는 전체 응용 프로그램에 대해 서버 생성 HTML에 연결됩니다. 이 과정을 수화라고 합니다.
    • 사이트를 인터랙티브하게 만듭니다.
각 단계의 대상이 전체 애플리케이션임을 강조했습니다.
이는 다음 단계가 시작되기 전에 각 단계가 전체 앱에 대해 한 번에 완료되어야 했기 때문입니다.
응용 프로그램의 일부가 다른 부분보다 느린 경우 이는 효율적이지 않습니다.
SSR WG 토론에(the SSR WG discussion)서 언급된 예를 살펴보겠습니다.
여기에서 우리 애플리케이션에는 Post 및 Comments가 포함된 NavBar, SideBar 및 RightPane이 있습니다.
<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Comments />
  </RightPane>
</Layout>
'Comments' 부분은 사용자가 관심을 갖는 애플리케이션의 가장 중요한 부분입니다.
<Comments> 컴포넌트는 많은 양의 데이터에 대한 값비싼 API 요청을 포함하고
많은 JavaScript 로직을 포함한다고 가정해 보겠습니다.
이제 이 애플리케이션의 SSR 관점에서의 문제점을 살펴보겠습니다.

React 18 이전 SSR의 문제점은 무엇입니까?

1. 무언가 사용자에게 보여주려면 필요한 데이터가 이미 먼저 다 있어야 합니다.

앞에서 보았듯이 사용자에게 무엇이든 보여주기 전에 모든 데이터를 가져와야 합니다.
즉,  많은 양의 데이터에 대해 시간이 걸릴 수 있는 모든 코멘트를 가져오는 작업이 필요함을 의미합니다.
그 동안 사용자가 화면에서 아무 것도 볼 수 없기 때문에 비효율적입니다.
우리에게는 아래 두 가지 선택지밖에 없습니다.
  • 서버에서 HTML 전송 지연 (그대로)
  • HTML에서 <Comments />을 제외합니다.
    • 이렇게 하면 클라이언트에서 <Comments />을 렌더링하는 데 오버헤드가 발생합니다.
이 두 가지 옵션 모두 좋지 않습니다.

2. 수화를 시작하기 전에(페이지를 인터랙티브 하게 만는 작업을 시작하기 전에) 모든 html / js를 로딩해야 합니다.

수화를 시작하기 전에 모든 JavaScript 코드를 로드해야 한다는 것을 알고 있습니다.
<Comments> 컴포넌트에는 복잡한 JavaScript 로직이 많이 포함되어 있어 로드하는 데 시간이 걸립니다.

 

NavBar, SideBar, Post에 대한 JS 코드가 로드되어도 수화가 시작되지 않습니다.
 
다시 말하지만, 우리에게는 두 가지 선택이 남아 있습니다.
  • 모든 JS 코드가 로드될 때까지 수화를 지연합니다. 
  • Comment에 코드 분할을 사용하고 별도로 로드합니다.
    • 그러나 이것은 이상적이지 않습니다. 이는 서버 HTML에서 Comment를 제외해야 함을 의미합니다.
    • 그렇지 않으면 React는 이 HTML 덩어리로 무엇을 해야 하는지 모르고 수화 중에 버릴 것입니다.

3. 페이지 상호작용을 위해 모든 것들을 수화해야 합니다. 페이지는 한번에 인터랙티브하게 됩니다.

예를 들어 <Comments> 컴포넌트에는 이벤트 핸들러를 연결하는, 꽤 많은 시간이 걸리는 값비싼 렌더링 로직이 있습니다.
아시다시피 수화는 한 번에 이루어집니다. 즉, 수화가 시작되면 React는 해당 작업이 완료될 때까지 멈추지 않습니다.
결과적으로 모든 컴포넌트가 상호 작용하기 전에 모든 컴포넌트가 수화될 때까지 기다려야 합니다.
사용자가 실수로 게시물을 클릭한 경우를 생각해 보십시오. 이제 그는 홈페이지로 이동하려고 합니다.
하지만 수화 작업으로 인해 애플리케이션이 정지되었습니다.
이로 인해 홈페이지 링크가 NavBar에 표시되더라도 사용자는 탐색할 수 없습니다. (수화가 완료 되기 전까지)
사용자에게는 시간 낭비입니다!

해결책

모든 문제에 대한 솔루션을 제공하는 React 18의 새로운 Suspense SSR 아키텍처가 있습니다!
폭포수 모델을 따르는 대신 작업을 쪼갭니다.

Fetch data (server) → Render to HTML (server) → Load JS code (client) → Hydrate (client)

이를 통해 전체 앱 대신 화면의 일부에 대해 이러한 각 단계를 따를 수 있습니다.
이에 대해 더 자세히 살펴보겠습니다.

React 18의 HTML 스트리밍 및 선택적 수화

Suspense는 일부 코드가 로드될 때까지 '기다리고' 코드가 로드를 완료하기를 기다리는 동안 로더를 지정하도록 하는 것입니다.
React 18에는  Suspense에 의해 잠금 해제된 두 가지 주요 SSR 기능이 있습니다.
  1. 서버에서 HTML 스트리밍:
    • renderToString에서 새 renderToPipeableStream 메서드로 전환합니다.
  2. 클라이언트의 선택적 수화:
    • 클라이언트 createRoot 내부에서 <Suspense>로 앱의 일부를 래핑합니다.
앞에서 논의한 예를 고수하면 <Comments> 컴포넌트가 문제라는 것을 알 수 있습니다.
따라서 해당 컴포넌트를 <Suspense>로 래핑하고
데이터가 준비될 때까지 화면에 <Spinner /> 컴포넌트를 표시해야 한다고 React에 알려줍시다:


첫번째 문제 해결 : 모든 데이터를 가져오기 전에 HTML 스트리밍

Comments의 데이터 페치가 완료되기 전에 뭔갈 보여줍시다.
<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>
우리는 React가 Comment를 기다리지 않고 나머지 애플리케이션에 대해 HTML 스트리밍을 시작하도록 지시합니다.
Comment는 Spinner placehloder로 대체됩니다.

 <main>
  <nav>
    <!--NavBar -->
    <a href="/">Home</a>
   </nav>
  <aside>
    <!-- Sidebar -->
    <a href="/profile">Profile</a>
  </aside>
  <article>
    <!-- Post -->
    <p>Hello world</p>
  </article>
  <section id="comments-spinner">
    <!-- Spinner -->
    <img width=400 src="spinner.gif" alt="Loading..." />
  </section>
</main>

Comment 데이터가 서버에서 준비되면
React는 HTML을 '올바른 위치'에 배치하기 위해 <script> 태그와 함께 추가 HTML을 동일한 스트림으로 보냅니다.
React 자체가 클라이언트에 로드되기 전에도 Comment용 HTML이 로드됩니다. 정말 멋지군요!

이것을 '스트리밍 HTML'이라고 합니다.

두번째 문제 해결 : 모든 코드가 스트리밍되기 전에 (부분적으로 먼저) 페이지 수화

가능한 컴포넌트들을 먼저 인터랙티브하게 합니다
<Suspense>에 Comments를 래핑함으로써
React에 페이지의 나머지 부분이 스트리밍뿐만 아니라 수화를 해제하도록 지시합니다!
이것을 '선택적 수화'라고 합니다.
선택적 수화 덕분에 JS의 무거운 부분이 페이지의 나머지 부분이 상호 작용하는 것을 막지 않습니다.
아래 이미지에서 <Suspense>를 사용하면 <Comments> 컴포넌트가 로드되기 전에 앱을 수화할 수 있습니다.
그런 다음 React는 JS 코드가 로드된 후 Comment 섹션을 수화하기 시작합니다.

세번째 문제 해결 : 모든 HTML이 스트리밍되기 전에 페이지 수화

2번과 동일한 맥락입니다.
<Suspense>로 Comment을 래핑함으로써 얻을 수 있는 또 다른 이점은 수화가 더 이상 사용자 상호 작용을 차단하지 않는다는 것입니다! 아래 이미지에서 <Comments> 컴포넌트가 수화 중인 경우에도 SideBar를 클릭할 수 있음을 알 수 있습니다.
Comment에 대한 JavaScript 코드가 로드되면 페이지가 완전히 인터랙티브하게 됩니다.

수화에 우선 순위 부여하기

<Suspense>에 래핑된 여러 컴포넌트가 있다고 가정합니다.
<Layout>
  <NavBar />
  <Suspense fallback={<Spinner />}>
    <Sidebar />
  </Suspense>
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>
React는 트리에서 먼저 찾은 Suspense 경계(이 경우 SideBar ) 먼저 두 개의 바운더리 모두를 수화하려고 시도합니다.
suspense가 없는 부분은 수화가 필요 없다 가정
사용자가 Comment 섹션과 인터랙션을 시작했다고 가정해 보겠습니다.
  • hydration은 짧은 간격으로 일어납니다
    • 따라서 사용자 클릭을 허용합니다.
    • React는 댓글이 더 긴급하다고 가정하고 Comment 섹션을 먼저 인터랙티브하게 만듭니다.
      • React는 클릭 이벤트의 캡처 단계에서 Comment를 동기식으로 수화합니다.
    • 그 후에는 사이드바를 계속 수화합니다.

우리의 세 번째 문제를 부분적 우선 인터랙션 적용으로 해결하였습니다!

(정리 : 우선순위는 상단 서스펜스 선처리.

하지만 사용자가 하단 서스펜스 부분을 클릭하거나 하면

인터랙션이 긴급하게 필요한 것으로 보고 수화 우선순위를 재조정함)

 

<Suspense> 컴포넌트의 이러한 개선 사항은 많은 SSR 문제를 해결했습니다.

Suspense를 위해 많은 작업을 해준 React 팀에 감사드립니다!

 

 

 

더보기 

https://github.com/reactwg/react-18/discussions/37

 

New Suspense SSR Architecture in React 18 · Discussion #37 · reactwg/react-18

Overview React 18 will include architectural improvements to React server-side rendering (SSR) performance. These improvements are substantial and are the culmination of several years of work. Most...

github.com

 

반응형