Nextjs 13 버전의 각종 기능중 app routing이 stable 버전으로써 사용되기 시작했습니다. app routing과 관련된 다양한 기능과 동작들이 있지만 app routing 방식의 특징중 가장 주목되는 점은 모든 컴포넌트가 기본적으로 Server Component로 동작한다는 점입니다.
Server Component가 Next에서 어떻게 동작하는지 간단히 알아보고 이를통해 무엇이 가능해질 것인지에 대해한 고민을 공유하고자 합니다.
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의 업데이트로 원하는대로 클라이언트 컴포넌트, 서버 컴포넌트로써 컴포넌트를 활용할 수 있게 되었습니다.
Server Component라는 개념은 React 18 버전에서 도입된 개념으로 이름과 같이 서버 컴포넌트는 클라이언트 단이 아닌 서버에서 동작하는 컴포넌트입니다. 따라서 서버에서 렌더되고 요청됩니다. 만약 클라이언트 번들에서 서버 컴포넌트를 찾고자 한다면 존재하지 않을것입니다.
Next13에서의 각각의 컴포넌트는 다음의 특징을 가지고 있습니다.
두가지 컴포넌트의 특징에따라 활용한 페이지를 그림으로 그려보면 다음과 같은 형태로 서버, 클라이언트가 사용될 것입니다. 사용자와 상효작용이 필요한 버튼, input 등의 컴포넌트들은 클라이언트 컴포넌트로, 사진, 글 등 서버에서 데이터를 불러와 그리거나 상호작용이 불필요한 컴포넌트는 서버 컴포넌트로 구현된 모습입니다.
그렇다면 새롭게 추가된 서버 컴포넌트를 사용함으로써 얻을 수 있는 이점은 무엇이 있을까요?
이런 장점과 더불어 개발자는 전략에 따라 어느 컴포넌트가 필요할지에 관한 고민이 들 수 있습니다. 다음은 Next공식문서에 포함된 각각 컴포넌트를 비교한 그림입니다.
이와같이 Next 공식문서에서 제시하는 사용 케이스를 바탕으로 한다면 상황에 따라 Server Component, Client Component중 어떤 컴포넌트를 활용해야 할까에 대한 가이드를 얻을 수 있을것입니다.
서버에서 렌더링되는 Server Component와 기존에 익숙한 서버 사이드 렌더링은 모두 서버사이드에서 렌더링 된다는 사실을 인지하였습니다. 그렇다면 두 SSR과 RSC 모두 동일한 렌더링 방식으로 동작할까요?
기존의 SSR에서 사용자가 완성된 하나의 페이지를 눈으로 보기까지 몇 가지 단계를 거쳐야 했습니다.
위와 같이 순차적인 동작을 수행하는 과정을 요약하자면 오직 서버에서 데이터 fetch를 통해 페이지를 위한 HTML을 한번에 렌더링 하고, React는 서버로부터 전송된 페이지의 모든 구성요소를 hydrate하는 역말만 수행합니다. 따라서 모든 데이터 fetch가 끝나고 페이지 렌더링이 마무리된 화면이 사용자에게 보여집니다.
SSR과 달리 RSC(React Server Component)는 스트리밍으로 렌더링 됩니다.
스트리밍을 사용하면 페이지의 HTML을 더 작은 청크로 분해하고 해당 청크를 서버에서 클라이언트로 점진적으로 보낼 수 있습니다.
이렇게 하면 UI가 렌더링되기 전에 모든 데이터가 로드될 때까지 기다리지 않고 페이지의 일부를 더 빨리 표시할 수 있습니다.
스트리밍은 각 구성 요소가 청크로 간주 될 수 있기 때문에 React의 컴포넌트 모델과 잘 결합됩니다. 우선순위가 더 높거나 데이터에 의존하지 않는 컴포넌트(예: 레이아웃, 스켈레톤)가 먼저 전송될 수 있으며, 우선순위가 낮은 컴포넌트(예: 매치리스트, 랭킹 테이블)는 데이터를 가져온 후 동일한 서버 요청으로 보낼수 있습니다.
이처럼, 기존의 SSR을 활용할때 모든 페이지의 렌더링을 진행하고 모든 렌더링이 마무리 되기까지 기다려야 한다는 특징으로인해 서버 사이드 렌더링을 활용한 페이지에서는 어떻게 사용자에게 스켈레톤 UI 혹은 로딩 UI를 보여주어야 하는가에 관한 고민이 있었습니다.
그러나 스트리밍 방식으로 동작하는 RSC가 가진 특징과 결합된 React.Suspense 를 활용한다면 SSR을 적용함에도 적절한 스켈레톤 UI 혹은 로딩 UI를 표현 할 수 있게 되었습니다.
Next13의 app routing의 적용은 React Server Component의 활용을 의미합니다. Server Component가 가져올 수 있는 다양한 이점을 활용 할 수 있게 될것입니다. 서버의 자원에 접근하고 번들 사이즈가 줄어드는 등 의 장점은 조금더 서버 컴포넌트에 대해 탐구해야 활용하고 체감할 수 있으리라 생각합니다. 현재 단계에서 가장 직관적으로 겪게될 장점은 두 가지라 여겨집니다.
기존의 pages 디렉토리를 활용한 라우팅에서 SSR 혹은 SSG 페이지를 구현하고자 한다면 반드시 최 상단 page 컴포넌트 에서 getServerSideProps / getStaticProps 함수를 통해 데이터를 fetch하고 서버사이드에서 fetch한 데이터를 각각의 컴포넌트 혹은 페이지에 props로 넘겨 사용해야 했습니다.
이는 컴포넌트 단위의 데이터 fetch를 수행하고자 할때 괴리감이 느껴지며 반드시 실제 데이터를 활용하는 컴포넌트까지 도달하기에 props drilling이 불가피 했습니다. 반면 서버 컴포넌트를 활용하면 실제 데이터가 필요한 컴포넌트에서 fetch를 수행하며 getServerSideProps / getStaticProps 함수의 활용은 더이상 필수가 아니게 되었습니다.
<Suspense>
컴포넌트의 자식 요소로딩이 완료되기 이전까지 원하는 fallback UI를 보여줄때 사용하는 React 컴포넌트 입니다.
기존의 방식은 명령적인 방법으로 조건에 따라 로딩UI를 분기로 나누는 방식이었습니다. Suspense의 도입으로 로딩을 처리하는 부분에 대한 책임과 데이터가 로딩 되었는지를 판단하는 책임을 Suspense에 맡기고, 컴포넌트는 정상적으로 데이터가 로딩되었을 때의 UI만 선언 할 수 있게합니다.
과거의 SSR 방식에서는 모든 페이지의 컴포넌트가 데이터 fetch를 진행하고 데이터를 이용한 렌더링을 마친 후 완성된 페이지의 HTML을 한번에 클라이언트에 그려내는 방식이기에 적절한 로딩UI를 적용하기 어려움이 있었습니다.
그러나 Streaming 방식으로 SSR이 동작하기에 데이터에 의존하지 않는 컴포넌트로써 fallbackUI를 Suspense에 전달해주면 데이터를 가져와 완성된 컴포넌트를 보여주기까지의 사이에서 로딩 UI, 스켈레톤 UI를 렌더링 할 수 있게됩니다.