서버 컴포넌트는 애플리케이션의 서버 부분에서 렌더링되는 컴포넌트로,
React 팀에서 2020년 12월 새로운 애플리케이션 아키텍처로 RSC(React Server Component) 를 소개하며 시작되었습니다.
그리고 React 18v 이후에 NextJS 에서도 사용할 수 있도록 업데이트 되었습니다.
서버 컴포넌트와 클라이언트의 개념이 SSR 과 CSR 은 아닙니다.
클라이언트 컴포넌트이지만 미리 골격을 만들어놓을 수 있다면 서버에서 미리 렌더링해 정적인 HTML 을 만들고
인터랙션이 필요한 부분을 클라이언트 단에서 사용자와 상호작용하게 해주는 것입니다. SSR 과 CSR 에 대해서는 다음 포스팅에서 다룰 예정입니다.
해당 포스팅에서는 서버 컴포넌트와 클라이언트 컴포넌트 각각의 특징과 차이, 구분 전략부터 실무에서 쓸 수 있는 혼합 전략을 정리했습니다.
Server Component
RSC는 UI를 서버에서 렌더링하고 필요한 경우 선택적으로 캐시할 수 있습니다. 이는 웹 애플리케이션의 성능 및 사용자 경험을 향상시키는 데 도움이 되는데요, NextJS 에서는 렌더링 작업이 경로 세그먼트에 따라 더 나눠져 스트리밍 및 부분 렌더링을 가능하게 하는데, 세 가지 서버 렌더링 전략이 있습니다.
- 정적 렌더링 : 이전에 렌더링된 정적 페이지를 제공하여 속도와 성능을 향상
- 동적 렌더링 : 요청 시에 동적으로 페이지를 렌더링하여 사용자에게 최신 콘텐츠 제공
- 스트리밍 : 렌더링이 진행되는 동안 서버가 데이터를 조금씩 클라이언트로 전송하여 페이지를 조금씩 표시할 수 있게 함
서버 컴포넌트는 서버에서 동작하는 컴포넌트이기 때문에 브라우저 환경의 코드, API(예시로 event listener, react hooks) 를 사용할 수 없는데,
Node 환경에서 제공해주는 node API 는 사용할 수 있습니다. 서버 컴포넌트는 클라이언트 컴포넌트를 포함할 수 있지만,
반대로 클라이언트 컴포넌트는 서버 컴포넌트를 포함할 수 없습니다. 서버 컴포넌트의 장점을 간략하게 정리한다면 아래와 같습니다.
- Data Fetching
- RSC 는 서버에서 렌더링되므로, 데이터를 가져오는 작업도 서버에서 수행되면서 data 를 가져오는 데 걸리는 시간과 클라이언트가 요청해야 하는 횟수를 줄일 수 있음
- Security
- 서버에 민감한 데이터 및 로직을 유지할 수 있어 보안에 용이 (토큰 같은 정보를 클라이언트에 노출하지 않고 서버에서 안전하게 처리 가능)
- Caching
- 결과를 캐싱하고 후속 요청 및 사용자 간에 재사용함으로써 요청마다 수행되는 렌더링 및 데이터를 가져오는 양을 줄이므로 성능 향상 및 비용 절감 가능
- Bundle Sizes
- 클라이언트 자바스크립트 번들 크기에 영향을 미치지 않도록 서버에 큰 종속성을 유지할 수 있는데, 클라이언트에서 분석 및 실행할 필요없는 코드들을 서버 컴포넌트를 사용함으로써 번들 사이즈를 줄일 수 있음
- Initial Page Load and First Contentful Paint
- 서버에서는 HTML 을 생성하여 사용자가 페이지를 즉시 볼 수 있도록 하여 ㅊ초기 페이지 로딩 속도가 빠름
- Streaming
- 렌더링 작업을 청크로 나누어 클라이언트로 스트리밍할 수 있어 사용자는 페이지 전체가 서버에서 렌더링 될 때까지 기다릴 필요 없이 일부분을 먼저 볼 수 있음
Client Component
클라이언트 컴포넌트는 브라우저 환경에서 실행되며, 사용자 상호작용 및 상택, 생명주기 메서드,
window 혹은 document 와 같은 브라우저 API 에 접근할 수 있습니다. React 의 모든 기능 (useState, 이벤트 핸들러 등) 을 사용할 수 있으며,
사용자 상호 작용을 처리할 수 있는 컴포넌트이므로 즉각적인 UI 업데이트, 상호작용 가능한 UI를 제공해야 할 때 용이합니다.
Server Component vs Client Component 차이와 구분 전략
서버 컴포넌트와 클라이언트 컴포넌트의 차이를 상황 별로 정리헤보겠습니다.
렌더링 위치와 실행 환경 관점에서는,
- 서버 컴포넌트는 서버에서 실행되고, 브라우저는 완성된 HTML 을 받습니다. React 컴포넌트지만 브라우저에서 동작하지 않고, node 환경에서 실행되므로 DB 쿼리, 파일 시스템 접근이 가능합니다.
- 클라이언트 컴포넌트는 브라우저에서 실행되고, React 앱의 일부로 번들링됩니다. HTML 은 SSR 단계에서 내려오더라도, 클라이언트에서 하이드레이션(이벤트 바인딩)을 거쳐 동작합니다. 브라우저 API와 React Hook 사용이 가능합니다.
번들 크기와 성능의 관점에서는,
- 서버 컴포넌트는 JS 번들에 포함되지 않기 때문에 클라이언트에 불필요한 JS 코드를 전달하지 않음으로써 초기 로딩 속도를 개선하고, 네트워크 지연을 최소화할 수 있습니다.
- 클라이언트 컴포넌트는 클라이언트 번들에 포함되므로 코드 크기가 증가하고, 하드레이션 과정이 필요해 초기 렌더링 속도가 서버 컴포넌트보다 느릴 수 있으나 UI 상호작용을 위해 필수로 필요합니다.
사용할 수 있는 기능과 사용하지 못하는 기능으로는,
- 서버 컴포넌트는 클라이언트 훅, 브라우저에서 제공하는 API 사용이 불가능하지만, DB, 파일, 서버 비밀키에 접근 가능하고 async/await 데이터 패칭이 가능합니다.
- 클라이언트 컴포넌트는 DB 직접 접근이 불가능하지만 (API 경유 필요) 상태 관리와 Hook 을 사용할 수 있고 이벤트 핸들링이 가능합니다.
마지막으로 SEO 와 데이터 패칭 관점으로는,
- 서버 컴포넌트는 SSR 과 유사하게 서버에서 미리 렌더링된 HTML 을 제공하고, 검색 엔진 최적화(SEO)에 유리합니다.
- 클라이언트 컴포넌트는 데이터는 클라이언트에서 패칭해야 하므로 초기 HTML 은 비어 있을 수 있고, SEO에는 불리하지만 사용자 경험에는 적합합니다.
Server Component & Client Component 혼합 전략 (실무 팁)
서버 컴포넌트 안에서 클라이언트 컴포넌트를 불러올 수 있다는 특징을 이용하여, 상위 서버 컴포넌트에서 데이터 패칭을 하여 속도를 향상 시키고, 하위 클라이언트 컴포넌트에 데이터를 전달하여 UI 인터랙션을 수행하도록 코드를 구현하면 됩니다.
“ 기본은 서버 컴포넌트로, 필요한 경우 “use client” 를 사용 “
// 예시 코드
// Server Component
export default async function Posts() {
const posts = await getPostsFromDB();
return <ul>{posts.map(post => <Post post={post} />)}</ul>;
}
// Client Component
"use client";
import { useState } from "react";
export default function Post(post) {
useEffect(() => {}, [])
return <>{post}</>;
};
Reference