리액트 서버 컴포넌트, 간단하게 알아보기

React
리액트 서버 컴포넌트, 간단하게 알아보기

안녕하세요! 오늘은 리액트 서버 컴포넌트에 대해서 알아보려고 합니다.


최근 리액트 생태계에서는 서버 컴포넌트 개념이 등장 함으로써 많은 개발자들의 주목을 받고 있더라고요. 저는 서버 컴포넌트에 대해서 정말 대충 알고만 있었는데, 이참에 한번 제대로 알아보면 좋을것 같아서 글 주제로 삼게 되었네요. 😃

1. 서버 컴포넌트란? 🤔

리액트 서버 컴포넌트리액트 18 버전에서 추가된 기능이며, 서버 측에서 컴포넌트를 실행하여 미리 렌더링된 마크업을 생성후, 클라이언트에게 전달되어 사용자에게 콘텐츠를 보여주는 방식입니다.


기존에 Next.js에서는 Page 단위의 컴포넌트에서만 백엔드 리소스에 접근이 가능했지만, RSC를 사용하게되면 일반적인 단위의 컴포넌트에서 접근 가능하다는 특징이 존재합니다.

백엔드 리소스: 데이터베이스, fs, node-fetch 등의 서버에서 접근 가능한 리소스

import fs from 'fs';
import db from 'db';

// 서버 컴포넌트 예제
const ServerComponent = () => {
  const file = fs.readFileSync('../'); // 파일 시스템 접근 가능
  const data = db.post.load(); // 데이터베이스 접근 가능

  return <div></div>;
};

서버에서 렌더링된 컴포넌트는 직렬화(Serialization) 가능한 상태로 클라이언트에게 데이터가 전달되는데요. 이때 함수(Function), 날짜(Date) 등의 데이터는 직렬화 불가능한 데이터로 취급되므로 서버 컴포넌트에서 작성할 수 있는 코드가 제한되어 있다는 특징이 있습니다.


대표적으로 서버 컴포넌트에서 작성할 수 없는 코드들은 아래와 같습니다.

  • useState, useEffect, useReducer 등의 State Hooks
  • DOM, 브라우저 API를 사용한 코드 (이벤트 리스너 포함)

사용자 이벤트 등의 코드를 작성할 수 없다는 특징을 보고, 제 생각에는 서버 컴포넌트에서는 Data Fetching이 대부분의 역할을 수행하며 이에 따른 컴포넌트 분리에 대한 중요도가 더욱 커질거같네요. (긍정적으로는, 데이터와 UI를 서버 or 클라이언트 컴포넌트로 잘 분리하는 능력이 향상 될거같아요.)

2. 서버 컴포넌트의 이점 🔍

2-1. 환경별 리소스 접근

환경별 리소스에 접근하기 좋다는 말은 위에서 설명드린 서버 컴포넌트의 특징 중 하나인 백엔드 리소스에 접근이 가능하다는 특징도 해당되는 개념인데요, 이를 조금 더 자세하게 설명드리려고 합니다.


서버 컴포넌트의 백엔드 리소스 접근에 관한 특징은 위에서 말씀 드렸으므로, 클라이언트 컴포넌트의 특징에 대해서 알아보겠습니다.

const SomeComponent = () => {
  const userInfo = localStorage.getItem('user'); // 서버에서 렌더링될경우 오류 발생

  return <div></div>;
};

만약 Next.js에서 컴포넌트를 제작하던 중, 해당 컴포넌트에서 로컬스토리지 혹은 쿠키 등 브라우저에서만 접근이 가능한 데이터를 사용하려고 했던 경험들이 있으실겁니다. 이런 경우에는 해당 컴포넌트를 dynamic import를 사용하여 서버에서 렌더링 되지 않도록 비활성화 해야합니다. 만약 이 컴포넌트가 여러곳에서 사용되는 컴포넌트라면, 일일히 dynamic import를 적용해줘야 하므로 귀찮아지죠. 😥


그래서! 이 경우에는 리액트 18에서 서버 컴포넌트와 같이 출시된 클라이언트 컴포넌트로 정의하여 사용하면, 해당 컴포넌트는 무조건 클라이언트에서만 렌더링되는 컴포넌트이므로 복잡하게 브라우저에서만 사용가능한 API를 편하게 사용할 수 있습니다.


기존에 처리하기 번거로웠던 백/브라우저 API 사용에 대해서 각각 서버/클라이언트 컴포넌트를 사용하여 해결할 수 있을것 같네요. 😃

2-2. 제로 번들 컴포넌트

기존에 모든 리액트 컴포넌트는 브라우저에서 다운로드 됩니다. 그러나 만약 해당 컴포넌트가 방대하거나, 외부 라이브러리를 많이 사용하게되면 컴포넌트의 번들 사이즈는 계속 증가됩니다. 코드 스플리팅으로 이를 최적화 할 수는 있지만, 어쨋거나 번들 사이즈가 커지게되면 사용자에게 보이는데 까지 오랜 시간이 걸릴수도 있지요.

{
  // 서버 -> 클라이언트로 전달되는 JSON 직렬화 데이터
  "A1": {
    "id": "./src/ClientComponent.tsx",
    "chunks": ["client1"],
    "name": ""
  },
  "C0": [
    "$",
    "@1",
    null,
    {
      "children": [
        "$",
        "span",
        null,
        { "children": "Hello! I'm Client Component!" }
      ]
    }
  ]
}

하지만 서버 컴포넌트는 해당 컴포넌트의 코드 및 번들이 서버에서 다운로드 되고, 번들이 브라우저로 전송이 되지 않습니다. 위에서 설명드린것처럼 직렬화(Serialization) 가능한 상태로 클라이언트에게 JSON 데이터를 전달하여 렌더링하는 방식이기 때문이죠. 제로 번들 사이즈의 특징을 지녔기때문에 기존보다 훨씬 빠르게 사용자는 컴포넌트를 볼 수 있을것 같습니다. 👍

2-3. 서버 사이드 렌더링과의 차이점

저는 처음에 서버 컴포넌트는 서버 사이드 렌더링과는 어떤점이 다른걸까? 하는 궁금증이 많이 생겼는데요, 그래서 관련 자료들을 찾아본 결과 아래의 특징으로 정리해볼 수 있었습니다.

1. 서버 컴포넌트는 클라이언트에게 전달되지 않는다. 기본적인 서버 사이드 렌더링은 JS 번들이 클라이언트에게 전달되지만, 서버 컴포넌트를 사용하게 되면 컴포넌트 JS 번들이 전달되지 않는다. (UX 향상)

2. 컴포넌트 단계에서 백엔드 리소스 접근이 가능하다. 기존에 Next.js에서는 최상위 단계(pages) 컴포넌트의 getInitialProps, getServerSideProps 함수에서만 접근이 가능했지만, 서버 컴포넌트를 사용하면 어느 단계에서든 접근이 가능하다.

3. 리렌더링 방식이 다르다. 서버 사이드 렌더링은 HTML 문서 자체를 업데이트 해야만 데이터를 최신화 하여 리렌더링이 가능하지만, 서버 컴포넌트는 클라이언트 상태(State)를 유지하면서 리렌더링 할 수 있다.

3-1. 추가적인 내용

서버 사이드 렌더링의 기본 개념인 서버에서 HTML 콘텐츠를 채워서 클라이언트에게 전달한다의 단계는 서버 컴포넌트가 렌더링 되기 전에 수행됩니다.


그래서 제가 추가적으로 들었던 생각은 서버 컴포넌트가 동작하기 위해선 서버 사이드 렌더링이 필요로 하며, 서버 사이드 렌더링의 보완 및 최적화 수단 느낌이 들었습니다. (서버 사이드 렌더링 안에 서버 컴포넌트가 포함되는 관계이지 않을까..)

4. 마치며 📌

지금까지 리액트의 서버 컴포넌트의 정의, 특징에 대해서 알아보았는데요. 저는 지금까지 서버 컴포넌트를 다뤄본 경험이 없다보니, 자료들을 이해하는데 어려움이 많았던것 같습니다. 😥


최근에 출시된 Next.js 13 버전app directory를 사용하면 서버 컴포넌트를 손쉽게 사용 가능하더라고요! 아직까지는 회사에서 Next.js 12 버전을 주로 사용하는데, 13 버전은 언제 넘어갈지는 아직 잘 모르겠네요. 프로덕에서 사용하는데 매우 안정화된 상태가 되면 한번 찍먹해보지 않을까 싶습니다. 🤔


이상으로 긴 글 읽어주셔서 감사합니다! 혹여나 글에서 잘못된 부분이 있다면, 피드백 남겨주시면 감사하겠습니다. 😛

온라인 마케팅의 핵심, UTM이 무엇일까?
이전글

온라인 마케팅의 핵심, UTM이 무엇일까?

온라인 마케팅의 효과를 분석, 추적할 수 있도록 도와주는 UTM 파라미터에 대해서 알아봅시다.

script 태그 로드방식, async와 defer 알아보기
다음글

script 태그 로드방식, async와 defer 알아보기

자바스크립트를 로드하는 방식인 async와 defer에 대해서 알아봅시다.