본문 바로가기

React

CSR, SSR 그리고 React v.18의 Suspense

작년 말 베타 버전이 배포된 React 18의 Suspense에 대해 알아보며, CSR(Client Side Rendering)과 SSR(Server Side Rendering)을 더 깊이 정리해보려 한다.

 

1. CSR Client Side Rendering

The Benefits of Server Side Rendering Over Client Side Rendering

클라이언트(웹 브라우저)가 HTML 뿐 아니라 JS파일까지 몽땅 받아다 직접 렌더링 해야 하는 CSR (Client Side Rendering)은 밀키트 사서 요리해먹기로 비유할 수 있다. React, Vue 등으로 구현되는 SPA(Single Page Application)이 CSR 방식을 이용한다.

처음 재료들을 받고 손질하기는 오래 걸리지만 한번 정리해두면 그때그때 먹고 싶은 대로 조리해 먹을 수 있다. 즉, 다양한 로직들이 포함된 큰 용량의 JavaScript 파일을 받아오고 API 요청 등에 시간이 걸려 초기 렌더링은 느리더라도 매번 서버에서 불러오지 않으므로 유저 인터렉션이 많은 경우 빠르게 동적으로 렌더링할 수 있다.

 

CSR의 치명적인 단점, 초기 렌더링과 SEO

초기 진입 속도가 느리다는 것은 그동안 사용자는 빈 화면을 보고 있어야 한다는 것을 뜻한다. 인터넷 상태가 좋지 않거나 구형 단말기인 경우 더욱 큰일이다. 또한 빈 HTML을 브라우저에게 넘기기 때문에 검색 엔진이 사이트 내용을 파악하지 못해 SEO(Search Engine Optimization)에는 매우 좋지 못하다.

 

다행히 검색 엔진 최적화 문제는 react-helmet과 react-snap을 이용해 해결해볼 수 있다.

 

react-helmet

페이지 별 meta 태그들을 미리 설정해주어 어떤 페이지인지 알려준다. 구글에 수면바지라고 검색했을 때 무신사, 쿠팡, G마켓 등 여러 사이트가 뜨는 것도 이커머스 내의 수많은 상품들이 각각 검색 최적화되어있기 때문이다.

미리 props로 데이터를 전달받는 메타 태그 폼을 컴포넌트로 생성해두고 페이지 컴포넌트 상단에 적용해주면 페이지 (또는 상품) 별로 직접 설정해주지 않아도 된다.

코드를 포함한 보다 자세한 설명은 여기를 참고하면 좋다.

 

react-snap

하지만 Single Page Application은 하나의 HTML만을 이용하기때문에 검색 엔진은 설정해준 meta 태그들을 여전히 인지하지 못한다.

따라서 동적으로 meta 태그들을 읽을 수 있도록 필요한 페이지들에 대한 HTML 파일을 생성한다.

그럼 SPA가 더 이상 아닌 거 아니야? 말 그대로 스냅샷이기 때문에 필요한 부분들을 캡처해둔 개념이라고 생각하면 좋을 것 같다. 웹 앱은 기존과 똑같이 SPA 기반으로 굴러간다.

 


 

2. SSR Server Side Rendering

초기 렌더링 문제를 해결하기 위해서 SSR 방식(식당가서 요리사가 해주는 음식 먹기)을 사용하는데, React 기반 SSR은 정확하게는 Universal Rendering이라고 표현한다. 최초 요청시에는 서버에서 Node.js 환경으로 HTML string을 생성하는 SSR이 이루어지고 이후 사용자와의 상호작용에서는 CSR로 작동하기 때문이다.

물론 anchor를 이용해 외부로 이동시키는 등 웹에 내장된 기능들을 제외하고 JavaScript 기반의 화려한 상호작용은 일어날 수 없지만, 빈 화면이 아닌 무언가 볼 수 있다는 것만으로도 유저 경험은 훨씬 나아진다. 이런 껍데기 HTML에 JavaScript까지 모두 불러와 이벤트 핸들이 가능해질 때 (우리가 알던 React 웹 앱이 완성된 상태) Hydraration을 마쳤다고 표현한다.

 

어떻게 구현하는가?

Universal Rendering에서는 최초 SSR 렌더링에 필요한 파일이후 Data fetching이나 Routing 과정에서 사용할 CSR 파일 모두 필요하다. 이렇게 두 파일을 생성하기 위해서는, @loadable-component 라이브러리를 이용해 코드 스플리팅을 구현하고 Webpack 및 babel 설정, 렌더링 서버 준비 단계를 거쳐야한다. 그리고 말만 들어도 까다로운 이 과정을 보다 편리하게 돕는 것이 Next.js라는 ZEIT 오픈 소스 프레임워크다. 

그 까다로운 과정에 대한 자세한 설명은 여기를 참고하자.

 


 

3.  Suspense

SSR에도 어떤 한 컴포넌트가 로딩이 오래 걸린다면 결국 최초 SSR 렌더링도 늦어진다는 한계가 있다. 이는 데이터를 불러오고, HTML을 렌더링 하고, 렌더링 된 HTML 코드를 브라우저에 로드한 뒤 Hydrate를 마치는 waterfall 흐름 때문에 여태 불가피했다. 즉 모든 컴포넌트의 HTML 코드가 브라우저에 띄워지기 전까지는 가벼운 역할을 하는 컴포넌트도 hydrate 되지 못해 유저 인터렉션이 발생할 수 없는 것이다.

 

React 18이 이걸 해냅니다 !

이제 <Suspense>를 이용해 선택적으로 hydration 할 수 있다. 몽땅 보여주거나 아무것도 못 보여주는 waterfall 흐름을 깨고, 빠르게 보여줄 수 있는 컴포넌트들은 최대한 빠르게 HTML 스트리밍 한 뒤 hydration 해주며무거운 컴포넌트는 HTML 렌더링 단계에서 spinner 등 다른 컴포넌트로 대체된다. 이때 뒤늦게 완성된 HTML 코드는 지금 보이고 있는 페이지 내 위치를 명시한 <script> 태그와 함께 마저 스트리밍 된다.

spinner로 대체된 컴포넌트 부분이 완성되지 않았음에도 불구하고, 나머지 컴포넌트들은 interactionable하다!!

 

심지어 hydration 되는 순서도 동적으로 정해진다.

컴포넌트 A(좌), B(우) 중 트리 상에서 상위인 A 먼저 hydrate 되는 상황이다. 이때 사용자가 B를 클릭하면 React는 이 이벤트를 기록하고 hydration 우선순위를 부여한다. 그리고 우선적으로 hydration을 마친 B에 대한 이벤트를 다시 실행시켜 인터렉션이 일어나도록 해준다.

 

 

isLoading State, 더 이상 필요 없다!

데이터 요청을 성공적으로 마쳤는지에 따라 isLoading 상태를 바꿔주어, 로딩 화면을 보여줄 것인지 받아온 데이터를 렌더링 할 것인지 결정했다. 하지만 Suspense를 이용한다면 로딩 여부를 분기하여 따져주지 않고 fallback으로 더 간단하고 깔끔하게 명시할 수 있다.

<Suspense fallback={<Spinner />}>
  <RealComponent />
</Suspense>

 


 

링크

SPA에서 서버사이드 렌더링을 구축하지 않고 SEO 최적화하기

React로 Next.js처럼 Server-side-rendering 구현하기

New Suspense SSR Architecture in React 18

노마드코더 리액트 18 Suspense 정리 영상