"Life is Full of Possibilities" - Soul, 2020

IT (프론트엔드)

CSR vs SSR: 웹 성능 최적화와 혼합 렌더링 방식, Hydration의 역할

m2ndy 2024. 10. 13. 23:21

 본 글은 2024.11.05에 마지막으로 수정되었습니다.  

 

 

CSR와 SSR

 
CSR와 SSR은 웹 어플리케이션을 렌더링 하는 대표적인 방법들이다. 본 글에서는 CSR와 SSR의 동작 원리와 이를 비교하며 장단점을 작성하려 한다.
 
 
 

CSR ( Client Side Rendering)

 
CSR은 브라우저에서 거의 모든 작업이 이루어지는 렌더링 방식이다. React의 경우 기본 HTML에 컴포넌트를 갈아 끼우는 SPA로 동작하기 때문에, 서버로부터 처음 받아오는 html 파일은 아주 간단한 HTML 상태다.
 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="/bundle.js"></script>
    <title>React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

 
이 기본 HTML 파일에는 <div> 태그만 있을 뿐, 서버에서 받아오는 데이터는 없는 상태다.
브라우저는 HTML 파싱을 통해 파일을 위에서부터 순차적으로 읽어나가다 <script> 태그를 발견하면 HTML 파싱을 멈추고 <script> 태그 내부의 파일을 다운로드하고 실행한다. 이 과정에서 자바스크립트 엔진이 번들 파일을 실행하고, API를 호출하는 로직이 있다면 서버로부터 데이터를 받아온다.

  • <script> 위치에는 소스 파일(번들), GA 태그 등 여러 종류의 파일들이 들어갈 수 있고, <script>에 defer나 async 속성이 없다면 파일의 다운로드 및 실행이 끝나야 HTML 파싱을 이어나갈 수 있다.

자바스크립트 소스 파일의 실행이 끝나 Render Tree가 만들어지면, 브라우저는 최종적으로 HTML와 자바스크립트가 결합된 화면을 출력한다.
 
CSR에서는 HTML과 소스 파일을 모두 다운받은 뒤에야 사용자는 화면을 볼 수 있다. 이로 인해 로딩 속도가 느릴 수 있으며, 초기 HTML을 빈 상태로 받아오기 때문에 검색 엔진 최적화 (SEO)에 불리하다. HTML을 기반으로 데이터를 수집하는 검색 엔진은 CSR의 기본 HTML 상태만을 읽게 된다. 따라서 비어있는 <div>만을 확인하여 데이터가 포함되지 않은 페이지로 인식할 수 있다.

CSR의 동작 방식

 
 
CSR의 장점으로는, 대부분의 과정을 클라이언트에서 처리하기 때문에 서버의 부하가 적다는 것이다. 또한, 데이터를 불러오는 동안 Suspense 등의 Fallback UI를 사용자에게 제공하여 동적인 화면 제공이 가능하다는 점이 있다.
 
 
 
 

SSR의 동작 방식

 
SSR은 서버에서 브라우저로 데이터가 포함된 HTML 완성본을 전달하는 방식이다. 즉, <div id="root">까지만 있는 기본 HTML이 아닌 서버에서 API를 호출하고 받은 데이터들이 모두 포함된 상태로 브라우저에 반환한다.
 

SSR의 동작 방식

 
브라우저는 SSR 서버에 HTML을 요청하고, SSR 서버에서 API 서버로 데이터를 응답받은 뒤 HTML에 끼워 넣어 완성된 HTML을 브라우저로 전달한다. 
 
앞서 소개한 기본 HTML에 데이터들이 아래와 같이 추가되어 있는 형태라고 볼 수 있다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="/bundle.js"></script>
    <title>React</title>
  </head>
  <body>
    <div id="root">
      <header>
        <h1>제목</h1>
      </header>
      <main>
      	<p>영화 리스트 소개 페이지</p>
        <ul>
          <li>
            <p>제목 : 분노의 질주</p>
            <p>개봉일 : 2000-01-01</p>
          </li>
          <li>
            <p>제목 : 엘리멘탈</p>
            <p>개봉일 : 2000-01-01</p>
          </li>
          <li>
            <p>제목 : 해리포터</p>
            <p>개봉일 : 2000-01-01</p>
          </li>
        </ul>
      </main>
    </div>
  </body>
</html>

 
 
이때 SSR은 onClick과 같은 이벤트 핸들러가 없는, 화면의 뼈대의 역할을 하는 HTML만을 반환하기 때문에 이벤트 핸들러가 전혀 적용되지 않은 상태다. (CSS는 적용된다.) 따라서 브라우저 엔진이 CSR와 마찬가지로 HTML을 순차적으로 읽어나가다 <script> 태그를 만나면 자바스크립트 번들을 다운로드한다. 이후 이벤트가 적용된다. (Hydration)
 
이 방식은 브라우저가 HTML을 먼저 렌더링 하기 때문에 검색 엔진 최적화로 SEO 측면에서 유리하고, 사용자는 초기 렌더링 속도가 빠르다고 느낄 수 있다. CSR에서는 자바스크립트 번들까지 실행되고 나서야 화면에 렌더링하지만, SSR에서는 서버로부터 텍스트나 이미지를 먼저 받아오기 때문에 FCP(First Contentful Paint)가 더 빠르다고 느끼게 된다.
 
반면, 모든 HTML을 서버에서 생성하므로 서버의 부담이 커질 수 있다. 네트워크 상태가 좋지 않은 경우나 데이터를 받아오는 서버의 성능이 좋지 않은 경우, 오히려 CSR의 속도가 더 빠르다고 느낄 수 있다.  또한, 브라우저에서 자바스크립트가 실행되기 전까지는 상호작용이 불가능하다는 단점이 있다. (TTI - Time to Interactive 에 불리)
(React 18 버전부터 SSR에서도 이 부분이 가능해졌다고 한다. - 키워드 : Selective Hydration, pipeToNodeWritable)
 
 

Hydration

 
Hydration을 한국어로 번역하면 '수화', 즉 수분 공급인데, React에서 생각해보면 정적인 HTML 페이지에 이벤트 핸들러를 부착하여 동적인 페이지로 만들어주는 것을 의미한다.
 
SSR로 이벤트 핸들러가 없는 HTML을 로드한 뒤, HydrateRoot를 통해 HTML에 이벤트 핸들러를 부착한다. 이 과정을 통해 사용자와 상호작용 할 수 있는 페이지가 완성된다.
 
 

CSR + SSR을 결합한 방식

 
초기 데이터는 SSR로 불러온 뒤, Hydration을 통해 동적인 페이지를 만들고 이후 페이지 전환이나 자주 변경되는 데이터는 CSR로 처리하는 방식이다. SSR에서는 데이터의 변경 시 index.html이 함께 변경되어 페이지가 리렌더링 되기 때문에 데이터 변경에 리소스가 많이 들게 된다. 따라서 데이터 변경이 비교적 자유롭고 부드러운 페이지 전환이 가능한 CSR 방식을 혼합하여 사용하면 CSR와 SSR의 장점을 모두 가져갈 수 있다.
 

const initialData = window.__INITIAL_DATA__;

hydrateRoot(
  document.getElementById('root'),
  <App movieList={initialData} />
);

 
 
이때, 전역 객체 초기화 작업이 필요한데, 이는 서버에서 미리 받아온 데이터와 브라우저에서 사용할 데이터를 일치시키는 작업이다.
 
SSR 서버에서 API 통신으로 받아온 데이터를 전역 객체를 통해 브라우저가 사용할 수 있게 된다. 만약 브라우저가 이 데이터를 받지 못하면 브라우저에서 동일한 API를 다시 호출하게 되어 중복 호출이 발생할 수 있다. 그 사이에 데이터가 변경되면 서버와 브라우저 간 데이터 불일치가 발생할 가능성이 있고, 이로 인해 HTML이 변경되어 화면이 리렌더링 되거나 DOM 트리가 다시 생성될 수 있다. 따라서 데이터의 일관성을 유지하고 불필요한 API 중복 호출을 막기 위해 전역 객체 초기화 작업이 꼭 필요하다.
 
 
 
 
 
 
참고 자료
SSR 시작하기 전 알아야 할 것들 (feat. CSR)
React의 Hydration에 대하여