노트

React 서버 컴포넌트(RSC)

React Server Components

프런트엔드#react#nextjs · 연결된 개념 19개

쉽게 말하면

React 서버 컴포넌트는 재료와 레시피(컴포넌트 코드, DB 라이브러리)는 주방에 두고 완성된 요리만 내보내는 방식이에요. 브라우저가 받는 자바스크립트가 줄고, 서버에서 DB를 바로 읽을 수 있어요.

비유가 깨지는 곳 완성된 요리는 손님이 다시 조리할 수 없어요. state, Effect, 이벤트 핸들러가 필요한 부분은 'use client'로 떼어 내요. 그 클라이언트 컴포넌트도 첫 로드 땐 서버에서 HTML로 미리 렌더돼요.

React 서버 컴포넌트(React Server Components, RSC)는 서버에서만 실행되고, 컴포넌트 코드가 아니라 실행 결과(직렬화된 엘리먼트 트리)만 클라이언트로 보내는 컴포넌트다. Next.js App Router에서는 'use client'를 붙이지 않은 컴포넌트가 기본으로 서버 컴포넌트다.

export default async function ProductList() {
  const products = await db.product.findMany()   // useEffect·fetch API 없이 바로
  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

왜 나왔나

CSR(Client-Side Rendering)은 JS(JavaScript)를 받고, 실행하고, 그제야 API(Application Programming Interface)를 불러 화면을 그렸다. SSR(Server-Side Rendering)은 HTML(HyperText Markup Language)을 먼저 주지만 데이터는 페이지 단위(getServerSideProps)로만 가져올 수 있었고, 하이드레이션이 필요 없는 부분까지 모든 컴포넌트의 JS를 보내야 했다.

  • 번들 0: 서버 컴포넌트와 거기서 쓴 라이브러리(DB(Database) 클라이언트, 마크다운·코드 하이라이터)는 클라이언트 번들에 들어가지 않는다. 하이드레이션할 컴포넌트도 줄어 쓸 수 있는 시점이 당겨진다(하이드레이션)
  • 컴포넌트 단위 데이터 접근: 트리 어디서든 DB·파일 시스템·내부 API에 직접 접근한다. 비밀 키가 브라우저로 새지 않는다
  • 클라이언트-서버 워터폴(Waterfall) 감소: 부모가 받아 온 결과로 자식이 다시 요청하는 왕복을 서버 안에서 끝낸다
  • 한 번만 실행된다: 요청당 한 번 실행되고 다시 렌더되지 않는다. 그래서 의존성 배열, 낡은 클로저, 메모이제이션 고민이 사라진다(부수 효과와 Effect)
sequenceDiagram
  participant B as 브라우저
  participant S as 서버
  Note over B,S: CSR
  B->>S: 페이지 요청
  S-->>B: 모든 컴포넌트의 JS 번들
  Note over B: JS 실행
  B->>S: API 호출
  S-->>B: 데이터
  Note over B: 그제야 화면을 그림
  Note over B,S: RSC
  B->>S: 페이지 요청
  Note left of S: 서버 컴포넌트 실행, DB 직접 조회
  S-->>B: 실행 결과 (직렬화된 엘리먼트 트리) + 클라이언트 컴포넌트 JS만
  Note over B: 서버 컴포넌트 코드·라이브러리는 번들에 없음

못 하는 것

state, Effect, 이벤트 핸들러, window·localStorage 같은 브라우저 API. 이런 상호작용이 필요한 부분만 클라이언트 컴포넌트로 떼어 낸다.

SSR과의 관계

SSR은 HTML을 만드는 기술이고, RSC는 컴포넌트를 서버에서 실행하는 모델이다. 둘은 함께 쓰인다. 서버 컴포넌트가 RSC 페이로드를 만들면, 첫 방문에는 그걸로 HTML까지 만들어 스트리밍하고, 이후 이동에는 페이로드만 받아 클라이언트 state를 유지한 채 바뀐 부분을 합친다. 쓰기는 서버 함수와 서버 액션, 렌더링 방식 전체는 CSR·SSR·SSG·ISR.

'use client'는 SSR을 끄지 않는다. 이 지시어는 모듈이 서버와 클라이언트 중 어느 쪽 그래프에 속하는지(번들 경계)를 정할 뿐이다. 클라이언트 컴포넌트도 첫 로드 때는 서버에서 HTML로 미리 렌더되고 브라우저에서 하이드레이션된다. 그래서 렌더 중 window를 바로 읽으면 클라이언트 컴포넌트여도 서버에서 에러가 난다(서버 렌더에서 훅이 하는 일).

출처: Josh W. Comeau - Making Sense of React Server Components · Next.js - Server and Client Components: On the server

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • React 엘리먼트와 JSX

    React 엘리먼트(React Element)는 "화면에 무엇을 그릴지"를 설명하는 불변의 평범한 JavaScript 객체다. JSX(JavaScript XML)는 이 객체를 만드는 문법이고, 컴파일하면 createElement(새 JSX 변환에서는 jsx()) 호출이 된다.

  • React.cache

    cache(fn)은 서버 컴포넌트(Server Component)용 React API(Application Programming Interface)로, 함수 결과를 한 번의 서버 요청(렌더) 동안만 기억한다. 같은 요청 안에서 같은 인자로 여러 번 불러도 실제 실행은 한 번이다. React 19 정식 API이며 서버 컴포넌트에서만 쓴다.

  • CSR bailout

    CSR(Client-Side Rendering) bailout은 미리 렌더(Prerender)되는 라우트에서 서버가 알 수 없는 값을 읽는 클라이언트 컴포넌트를 만나, 가장 가까운 Suspense 경계까지의 클라이언트 컴포넌트 트리를 서버 렌더에서 포기하고 브라우저 렌더로 넘기는 일이다. 대표 원인이 useSearchParams()다. 빌드 때는 ?q=신발 같은 쿼리를 모르기 때문이다.

  • React의 규칙과 렌더 순수성

    Rules of React는 React가 올바르게 동작하고 최적화하기 위해 코드가 지켜야 하는 규칙으로, 공식 문서가 세 가지로 정리한다. 핵심은 렌더가 순수한 계산이어야 한다는 것이다.

  • useSyncExternalStore

    useSyncExternalStore는 React 밖에 있는 상태(외부 스토어(External Store), 브라우저 API(Application Programming Interface))를 구독해 컴포넌트와 동기화하는 훅이다. 동시성 렌더링(Concurrent Rendering)에서도 모든 컴포넌트가 같은 스냅샷을 보게 보장해 tearing을 막는다(React 18+).

보기 옵션