본문 바로가기
React/Next.js

[Next.js] Server Component 와 Client Component

by dev수니 2023. 12. 7.
반응형

Next.js 에서 리액트 서버 컴포넌트와 클라이언트 컴포넌트를 사용할 수 있다. app 경로에서 사용하는 모든 컴포넌트는 기본적으로 서버 컴포넌트이다. 이 게시글에서 서버 컴포넌트와 클라이언트 컴포넌트를 알아보자.

Serialization 이란?

Serialization(직렬화) 는 서버 컴포넌트와 클라이언트 컴포넌트를 사용한다면 알고 있어야 할 중요한 주제이다.

Serialization은 객체가 파일 시스템, DB 또는 메모리에 저장될 수 있도록 하기 위해 객체를 바이트스트림으로 변환하는 과정이다.

메모리나 DB, 파일시스템에 어떤 것을 저장하기 위해서는 그것을 바이트 스트림으로 유지해 놓아야 한다.

제로 번들 컴포넌트

기존에 모든 리액트 컴포넌트는 브라우저에서 다운로드 된다. 그러나 만약 해당 컴포넌트가 방대하거나, 외부 라이브러리를 많이 사용하게되면 컴포넌트의 번들 사이즈는 계속 증가된다. 코드 스플리팅으로 이를 최적화 할 수는 있지만, 어쨋거나 번들 사이즈가 커지게되면 사용자에게 보이는데 까지 오랜 시간이 걸릴수도 있다. 하지만 서버 컴포넌트는 해당 컴포넌트의 코드 및 번들이 서버에서 다운로드 되고, 번들이 브라우저로 전송이 되지 않는다. 위에서 설명한 것처럼 직렬화(Serialization) 가능한 상태로 클라이언트에게 JSON 데이터를 전달하여 렌더링하는 방식이기 때문이다. 제로 번들 사이즈의 특징을 지녔기때문에 기존보다 훨씬 빠르게 사용자는 컴포넌트를 볼 수 있다.

리액트 서버 컴포넌트란?

리액트 서버 컴포넌트는 서버에서 렌더되고 요청되는 컴포넌트이다. 만약 클리이언트 쪽의 번들에서 서버컴포넌트를 찾는다면, 그것은 존재하지 않을 것이다.

  • 리액트 컴포넌트들을 백엔드 부분이라고 생각하면 이해가 쉽다.
  • 서버 컴포넌트는 onClick과 같은 상호작용성을 포함하지 않는다.
  • fallback과 함수들은 props 로 전달될 수 없다.
  • 서버 컴포넌트는 상호작용하지 않으며, React State가 필요하지 않다.
  • 서버 컴포넌트는 React Life Cycle Hooks를 사용하지 않는다.
  • DB나 파일시스템에 기반한 작업들이나, life cycle hooks나 상호작용성이 없는 다른 컴포넌트들이 리액트 서버 컴포넌트들의 예시이다.
  • DOM, 브라우저 API(ex document)를 사용한 코드 (이벤트 리스너 포함) 를 사용할 수 없다.

서버컴포넌트의 목적

웹팩 번들을 보면 "use client"로 표시되거나 웹팩 안에 포함되었는 클라이언트 컴포넌트들만 웹팩 번들에 포함된다.
일반적으로 브라우저(클라이언트)는 데이터를 요청하고, 해당 페이지에서 자바스크립트를 렌더한다. 그리고 api 데이터를 서버에 요청한다.
데이터가 받아지면, 그 데이터를 페이지에 렌더한다. 이런 과정들을 Hydration 으로 부른다.

이렇게 서버 컴포넌트는 클라이언트 사이드 번들을 포함하지 않는다. 이것은 적은 양의 클라이언트 사이드 번들을 우리가 전달할 수 있도록 해서, 사용자가 적은 js 코드를 다운받을 수 있도록 한다.

클라이언트 컴포넌트들의 특징

  • 클라이언트 컴포넌트는 onClick 같은 상호작용성을 포함한다.
  • 클라이언트 컴포넌트는 브라우저에서 렌더된다.
  • 클라이언트 컴포넌트는 "use client" 라고 선언하여 사용한다.
  • 만약 useState, useEffect 같은 리액트 lifecycle hooks 를 사용할 계획이라면, 그들은 클라이언트 컴포넌트에서 사용해야 한다.

리액트 서버 컴포넌트와 클라이언트 컴포넌트의 차이점

리액트 서버 컴포넌트는 서버에서 렌더되고 fetch 되는 반면, 클라이언트 컴포넌트는 클라이언트쪽에서 렌더되고 fetch 된다.
life cycle hooks 같은 상호작용성을 포한하는 컴포넌트라면 클라이언트 컴포넌트로 만들어야 한다. 그렇지 않다면 서버 컴포넌트로 작성한다.

만약, useState 나 useEffect 를 사용하는 백만개가 넘는 npm 패키지들은 어떻게 해야할까?
컴포넌트를 선언하는 곳이 중요하기 때문에 이는 중요하지 않다.

예시로, 클라이언트 컴포넌트 안에서 선언한 클라이언트 컴포넌트인 Calendar 는 잘 동작할 것이다.하지만 "use client"를 지운다면, 이 컴포넌트는 서버 컴포넌트가 되고 에러가 발생한다.

leafs 와 root node

최대한 서버에서 렌더되게 할 수 있는 것이 좋다. 따라서 클라이언트 컴포넌트를 최대한 leaf node 로 내리는 것이 좋다.
사용자들이 더 적은 자바스크립트 번들 파일을 다운받을 수 있도록 하는 것이 많은 이점을 얻을 수 있기 때문이다.

서버사이드 렌더링과 리액트 서버 컴포넌트의 차이

서버 컴포넌트는 서버 사이드 렌더링과 같지 않지만 상호 보완적인다. 서버 컴포넌트들은 html 반환하기 보다, 렌더된 UI에 대한 설명을 반환한다. 이러한 방식은 리액트가 상태값을 잃지 않고 존재하는 클라이언트 컴포넌트들과 데이터를 병합하게 한다.

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

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

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

서버 컴포넌트에서 클라이언트로 어떻게 props 전달해야 할까?

서버 컴포넌트에서 클라이언트 컴포넌트로 props를 전달할 때, 서버 컴포넌트들은 Serialization(직렬화) 되어야 한다.

Props로써 ()=>void 같은 함수를 전달할 수 없다. 대신 우리는 json으로 직렬화 될 수 있는 props들을 전달할 수 있다.

만약, 서버 컴포넌트에서만 사용해야 할 코드들을 명백히 서버 컴포넌트 안에서만 작동하도록 하고싶다면, "server-only" 라는 패키지를 사용하면 된다.

서버컴포넌트를 이용하여 data fetching 방식을 사용할 때의 장점

1. 클라이언트 단에서 돌아가지 않는 데이터베이스 및 api 등의 백엔드 서비스에 접근이 가능하다.

2. 보안 상 노출되면 안되는 키값들을 클라이언트 단에 드러나지 않게 할 수 있다.

3. data fetching 과 렌더링을 동일한 환경에서 수행할 수 있다.

4. 서버에 렌더링 캐싱이 가능하다.

5. 번들링할 자바스크립트 양이 줄어든다.

 

일반적으로 리액트에선 모든 작업이 클라이언트 단에서 이루어진다. Next.js 는 이러한 컴포넌트들을 서버에서 부분적으로 렌더링되는 페이지로 만들어서 클라이언트에서 모든 것을 부담하지 않아도 되게 개선하였다. 하지만 이 방식은 이렇게 서버단에서 생성된 HTML에 결국 클라이언트 단에서 hydrate 작업을 진행해야 한다는 것이다. 즉 추가적인 자바스크립트 코드가 필요하다는 의미다.

상황에 따라 서버 컴포넌트와 클라이언트 컴포넌트 중 더 적합한 것을 사용해야 한다.

 

Next.js에 따르면 우선 아래와 같은 기준을 두고, 가능한 경우 서버 컴포넌트로 만들고 따로 클라이언트 컴포넌트로 구현이 필요한 부분들만 추출하는 편이 좋다고 한다.

  1. data fetching이 필요한 경우 → 서버 컴포넌트
  2. 백엔드 자원에 접근해야 하는 경우 → 서버 컴포넌트
  3. 클라이언트에 드러내면 안 되는 민감한 정보가 있을 때 → 서버 컴포넌트
  4. 자바스크립트 코드를 줄여야 할 때 → 서버 컴포넌트
  5. click, change 리스너 등을 사용하여 대화형(상호작용) 컨텐츠를 구현하려는 경우 → 클라이언트 컴포넌트
  6. '상태(state)'을 활용하는 경우 → 클라이언트 컴포넌트
  7. 브라우저 상에서만 지원하는 API(예: local storage와 같은 웹 스토리지를 다루는 API)를 사용하는 경우 → 클라이언트 컴포넌트
반응형

댓글