2 min read

RSC Boundary 설계: Server/Client 분리 기준

React Server Components 경계를 기능 단위로 나눠 번들 크기와 복잡도를 동시에 관리하는 방법

RSC Boundary 설계: Server/Client 분리 기준 thumbnail

도입

RSC를 도입하면 데이터 로딩을 서버로 밀어낼 수 있어 첫 렌더 성능이 좋아진다. 하지만 무작정 Server Component로 바꾸면 상호작용이 많은 영역에서 오히려 코드 복잡도가 증가한다. 특히 팀 단위로 개발할 때는 "어디까지 서버인가"에 대한 합의가 없으면 컴포넌트 경계가 흔들린다.

이 글은 RSC 경계를 기능 기준으로 나누는 방식, 그리고 경계가 잘못됐을 때 나타나는 징후를 실무 관점에서 정리한다.

RSC Boundary 설계: Server/Client 분리 기준 커버
Wikimedia Commons 기반 무료 이미지

문제 정의

다음 상황이 반복되면 경계 설계가 잘못된 가능성이 높다.

  • 페이지마다 "use client"가 과도하게 붙어 초기 JS 번들이 급격히 커진다.
  • 서버에서 가져온 데이터를 다시 클라이언트 훅으로 중복 fetch한다.
  • UI 변경과 데이터 로딩 변경이 동시에 발생해 리뷰 범위가 커진다.
  • 캐시 정책이 페이지 단위로 흩어져 재검증 전략이 일관되지 않다.

핵심은 "UI 인터랙션"과 "데이터 오케스트레이션"을 같은 레이어에 두지 않는 것이다.

핵심 개념

구분Server Component에 두기 좋은 것Client Component에 두기 좋은 것
데이터 처리DB/API aggregate, 권한 기반 필터링실시간 로컬 상태, 폼 편집 상태
렌더링SEO 필요 영역, 초기 콘텐츠클릭/드래그/애니메이션 중심 영역
캐시revalidateTag, fetch cache브라우저 메모리 캐시
의존성Node 전용 SDK, 비공개 토큰DOM API, Web API

경계를 나눈 뒤에는 "클라이언트 섬(island) 최소화"를 목표로 둔다. 하나의 페이지에 상호작용 영역 2~3개 수준이면 대부분 충분하다.

코드 예시 1: 서버 경계에서 데이터 조합

import { cache } from "react";
import { getPostList, getPopularTags } from "@/lib/posts/repository";

const loadPostsPage = cache(async () => {
  const [posts, tags] = await Promise.all([
    getPostList({ limit: 30 }),
    getPopularTags({ limit: 20 }),
  ]);

  return { posts, tags };
});

export default async function PostsPageServer() {
  const { posts, tags } = await loadPostsPage();

  return (
    <>
      <PostsHero total={posts.length} />
      <PostsList posts={posts} />
      <PopularTags tags={tags} />
    </>
  );
}

코드 예시 2: 상호작용만 클라이언트 섬으로 분리

"use client";

import { useMemo, useState } from "react";

export function PostFilterIsland({ tags }: { tags: string[] }) {
  const [query, setQuery] = useState("");
  const [selectedTag, setSelectedTag] = useState<string | null>(null);

  const normalized = useMemo(() => query.trim().toLowerCase(), [query]);

  return (
    <section className="space-y-2">
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="검색어"
        className="w-full rounded-md border px-3 py-2"
      />
      <TagPills tags={tags} selected={selectedTag} onSelect={setSelectedTag} />
      <p className="text-sm text-muted-foreground">query={normalized || "(empty)"}</p>
    </section>
  );
}

아키텍처 흐름

Mermaid diagram rendering...

트레이드오프

  • 서버 중심으로 옮기면 번들은 줄지만, 컴포넌트 분할 규칙을 팀이 이해해야 한다.
  • 클라이언트 섬을 최소화하면 성능은 좋아지지만, UI 상태 전달 설계가 중요해진다.
  • 경계가 안정되면 페이지 성능 개선뿐 아니라 리뷰 범위도 작아진다.

정리

RSC 경계는 "서버냐 클라이언트냐"를 기술 취향으로 고르는 문제가 아니다. 데이터 책임과 상호작용 책임을 분리하는 아키텍처 결정이다. 경계 기준을 문서화하고 PR 체크리스트에 반영하면, 팀 전체 품질이 안정적으로 올라간다.

이미지 출처

  • Cover: source link
  • License: CC BY-SA 4.0 / Author: Lora Gilyard
  • Note: Wikimedia Commons 무료 라이선스 이미지를 다운로드 후 1600px 기준 JPG로 최적화했습니다.

댓글