Next.js Server vs Client Component

혜성·2024년 4월 24일
0
post-thumbnail

Nextjs 13 버전의 각종 기능중 app routing이 stable 버전으로써 사용되기 시작했습니다. app routing과 관련된 다양한 기능과 동작들이 있지만 app routing 방식의 특징중 가장 주목되는 점은 모든 컴포넌트가 기본적으로 Server Component로 동작한다는 점입니다.

Server Component가 Next에서 어떻게 동작하는지 간단히 알아보고 이를통해 무엇이 가능해질 것인지에 대해한 고민을 공유하고자 합니다.

Next App Routing의 변화

Next 13 버전의 변화중 가장 큰 변화는 app routing의 등장이라 할 수 있습니다. 기존에 Next에서 라우팅을 하는 방법은 pages 디렉토리를 통한 방법이었으나 Next 13에서는 app 디렉토리가 pages 디렉토리를 완전히 대체하게 됩니다. 그리고 그 app 디렉토리를 사용한 라우팅에서 가장 주목해야 할 특징은 app 디렉토리 내부에서는 기본적으로 모든 컴포넌트가 서버 컴포넌트로 동작한다는 점입니다.

만약, app 디렉토리에서 사용하는 컴포넌트를 클라이언트 컴포넌트로 사용하고자 한다면 아래와 같이 “use client” 를 선언해 주어야 합니다.

"use client" 

import React from "react"

const MyClientComponent = () => {
	return <h1>"i am client component"</h1>
}

export default MyClientComponent

우리는 Next의 업데이트로 원하는대로 클라이언트 컴포넌트, 서버 컴포넌트로써 컴포넌트를 활용할 수 있게 되었습니다.

React Server vs Client Component

Server Component라는 개념은 React 18 버전에서 도입된 개념으로 이름과 같이 서버 컴포넌트는 클라이언트 단이 아닌 서버에서 동작하는 컴포넌트입니다. 따라서 서버에서 렌더되고 요청됩니다. 만약 클라이언트 번들에서 서버 컴포넌트를 찾고자 한다면 존재하지 않을것입니다.

Next13에서의 각각의 컴포넌트는 다음의 특징을 가지고 있습니다.

Server Component

  • 서버 컴포넌트들은 onClick 같은 상호작용성을 포함하지 않는다.
  • fallback과 함수들은 props들로써 전달될 수 없다.
  • 서버 컴포넌트들은 상호작용 하지 않으며, 그들은 React State가 필요가 없다.
  • 서버 컴포넌트들은 리액트 Life Cycle Hooks들을 사용하지 않는다.
  • 데이터베이스나, 파일 시스템에 기반한 작업들이나, life cycle hooks들이나 상호작용성이 없는 다른 컴포넌트들이 리액트 서버 컴포넌트들의 예시이다.

Client Component

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

Next13에서 구현될 페이지

두가지 컴포넌트의 특징에따라 활용한 페이지를 그림으로 그려보면 다음과 같은 형태로 서버, 클라이언트가 사용될 것입니다. 사용자와 상효작용이 필요한 버튼, input 등의 컴포넌트들은 클라이언트 컴포넌트로, 사진, 글 등 서버에서 데이터를 불러와 그리거나 상호작용이 불필요한 컴포넌트는 서버 컴포넌트로 구현된 모습입니다.

Server Component의 장점

그렇다면 새롭게 추가된 서버 컴포넌트를 사용함으로써 얻을 수 있는 이점은 무엇이 있을까요?

  • 서버 컴포넌트를 통해 서버 인프라를 더 잘 활용 할 수 있습니다.
  • 데이터 fetch의 위치를 데이터 베이스와 더욱 가까운 서버로 이동할 수 있습니다.
  • 자유로운 서버 시소스 접근이 가능해 집니다. 데이터베이스, 파일 시스템 그리고 인터널 서비스 같은 서버 사이드 데이터 소스에 직접 접근할 수 있습니다.
  • 서버 컴포넌트들은 클라이언트 사이드 번들을 포함하지 않습니다. 이는 브라우저가 더욱 적은 자바스크립트 코드를 다운받도록해 성능 향상에 도움을 줄 수 있습니다.
  • lazy loading이 필요한 컴포넌트에 일일이 React lazy와 dynamic import를 적용할 필요가 없습니다.

언제 어떤 컴포넌트를 활용할까

이런 장점과 더불어 개발자는 전략에 따라 어느 컴포넌트가 필요할지에 관한 고민이 들 수 있습니다. 다음은 Next공식문서에 포함된 각각 컴포넌트를 비교한 그림입니다.

  • 앞서 살펴본 특징과 더불어 Server Component는 다음과 같은 상황에서 활용하기 적절합니다.
    • data를 fetch할때
    • 백엔드 리소스에 접근할때
    • access token, api key와 같이 클라이언트 사이드에서 다루기 민감한 정보를 다룰때
    • 서버사이드 의존도가 크거나 클라이언트 자바스크립드를 줄이고 싶을떄
  • Client Component는 다음과 같은 경우 활용하기 적절합니다.
    • onClick, onChange와 같은 이벤트 리스너를 활용할때
    • useState(), useEffect() 와 같은 라이프사이클 Hook을 다룰때
    • 브라우저에서만 동작하는 API
    • useState(), useEffect() 등을 활용한 커스텀 훅을 사용할때
    • React class 컴포넌트를 사용할때 (csr로만 동작)

이와같이 Next 공식문서에서 제시하는 사용 케이스를 바탕으로 한다면 상황에 따라 Server Component, Client Component중 어떤 컴포넌트를 활용해야 할까에 대한 가이드를 얻을 수 있을것입니다.

SSR과 RSC(React Server Component)의 렌더링

서버에서 렌더링되는 Server Component와 기존에 익숙한 서버 사이드 렌더링은 모두 서버사이드에서 렌더링 된다는 사실을 인지하였습니다. 그렇다면 두 SSR과 RSC 모두 동일한 렌더링 방식으로 동작할까요?

기존의 SSR 동작

기존의 SSR에서 사용자가 완성된 하나의 페이지를 눈으로 보기까지 몇 가지 단계를 거쳐야 했습니다.

  1. 주어진 페이지를 위해 필요한 모든 데이터를 fetch한다.
  2. 다음 으로 서버에서 페이지의 HTML을 렌더링 한다.
  3. 해당페이지의 HTML, CSS, JS 를 클라이언트로 전송한다.
  4. 인터렉티브 하지 않은 렌더링된 페이지가 사용자에게 보여진다
  5. React hydrates를 통해 인터렉티브한 페이지로 완성된다.

위와 같이 순차적인 동작을 수행하는 과정을 요약하자면 오직 서버에서 데이터 fetch를 통해 페이지를 위한 HTML을 한번에 렌더링 하고, React는 서버로부터 전송된 페이지의 모든 구성요소를 hydrate하는 역말만 수행합니다. 따라서 모든 데이터 fetch가 끝나고 페이지 렌더링이 마무리된 화면이 사용자에게 보여집니다.

RSC with Streaming

SSR과 달리 RSC(React Server Component)는 스트리밍으로 렌더링 됩니다.

스트리밍을 사용하면 페이지의 HTML을 더 작은 청크로 분해하고 해당 청크를 서버에서 클라이언트로 점진적으로 보낼 수 있습니다.

이렇게 하면 UI가 렌더링되기 전에 모든 데이터가 로드될 때까지 기다리지 않고 페이지의 일부를 더 빨리 표시할 수 있습니다.

스트리밍은 각 구성 요소가 청크로 간주 될 수 있기 때문에 React의 컴포넌트 모델과 잘 결합됩니다. 우선순위가 더 높거나 데이터에 의존하지 않는 컴포넌트(예: 레이아웃, 스켈레톤)가 먼저 전송될 수 있으며, 우선순위가 낮은 컴포넌트(예: 매치리스트, 랭킹 테이블)는 데이터를 가져온 후 동일한 서버 요청으로 보낼수 있습니다.

이처럼, 기존의 SSR을 활용할때 모든 페이지의 렌더링을 진행하고 모든 렌더링이 마무리 되기까지 기다려야 한다는 특징으로인해 서버 사이드 렌더링을 활용한 페이지에서는 어떻게 사용자에게 스켈레톤 UI 혹은 로딩 UI를 보여주어야 하는가에 관한 고민이 있었습니다.

그러나 스트리밍 방식으로 동작하는 RSC가 가진 특징과 결합된 React.Suspense 를 활용한다면 SSR을 적용함에도 적절한 스켈레톤 UI 혹은 로딩 UI를 표현 할 수 있게 되었습니다.

Next의 서버 컴포넌트 도입으로 얻을 수 있는것

Next13의 app routing의 적용은 React Server Component의 활용을 의미합니다. Server Component가 가져올 수 있는 다양한 이점을 활용 할 수 있게 될것입니다. 서버의 자원에 접근하고 번들 사이즈가 줄어드는 등 의 장점은 조금더 서버 컴포넌트에 대해 탐구해야 활용하고 체감할 수 있으리라 생각합니다. 현재 단계에서 가장 직관적으로 겪게될 장점은 두 가지라 여겨집니다.

No More getServerSideProps / getStaticProps

기존의 pages 디렉토리를 활용한 라우팅에서 SSR 혹은 SSG 페이지를 구현하고자 한다면 반드시 최 상단 page 컴포넌트 에서 getServerSideProps / getStaticProps 함수를 통해 데이터를 fetch하고 서버사이드에서 fetch한 데이터를 각각의 컴포넌트 혹은 페이지에 props로 넘겨 사용해야 했습니다.

이는 컴포넌트 단위의 데이터 fetch를 수행하고자 할때 괴리감이 느껴지며 반드시 실제 데이터를 활용하는 컴포넌트까지 도달하기에 props drilling이 불가피 했습니다. 반면 서버 컴포넌트를 활용하면 실제 데이터가 필요한 컴포넌트에서 fetch를 수행하며 getServerSideProps / getStaticProps 함수의 활용은 더이상 필수가 아니게 되었습니다.

React.Suspense를 활용한 SSR 컴포넌트 로딩 UI 적용

<Suspense> 컴포넌트의 자식 요소로딩이 완료되기 이전까지 원하는 fallback UI를 보여줄때 사용하는 React 컴포넌트 입니다.

기존의 방식은 명령적인 방법으로 조건에 따라 로딩UI를 분기로 나누는 방식이었습니다. Suspense의 도입으로 로딩을 처리하는 부분에 대한 책임과 데이터가 로딩 되었는지를 판단하는 책임을 Suspense에 맡기고, 컴포넌트는 정상적으로 데이터가 로딩되었을 때의 UI만 선언 할 수 있게합니다.

과거의 SSR 방식에서는 모든 페이지의 컴포넌트가 데이터 fetch를 진행하고 데이터를 이용한 렌더링을 마친 후 완성된 페이지의 HTML을 한번에 클라이언트에 그려내는 방식이기에 적절한 로딩UI를 적용하기 어려움이 있었습니다.
그러나 Streaming 방식으로 SSR이 동작하기에 데이터에 의존하지 않는 컴포넌트로써 fallbackUI를 Suspense에 전달해주면 데이터를 가져와 완성된 컴포넌트를 보여주기까지의 사이에서 로딩 UI, 스켈레톤 UI를 렌더링 할 수 있게됩니다.

0개의 댓글