2026년 03월 26일
최적화
이번 글에서는 Next.js App Router 환경에서 RSC (React Server Component) 와 Tanstack Query의 HydrationBoundary 를 활용하여 블로그 상세 페이지의 초기 렌더링 경험을 개선한 과정을 정리해보려고 합니다.
서비스를 운영하는 개발자라면 누구나 사용자가 불편함을 느끼지 않도록 페이지 진입 속도를 최적화하고, 단 0.1초라도 더 빠르게 화면을 구성하려는 목표를 갖고 있을 것입니다.
비즈니스 관점에서도 페이지가 빠르면 사용자는 자연스럽게 더 많은 콘텐츠를 소비하고 사이트에 오래 머무르게 됩니다. 반대로 렌더링이 1초 지연될 때마다 사용자 이탈률은 급격히 상승합니다. 블로그 상세 페이지는 콘텐츠 자체가 핵심인 만큼, 어떻게 하면 가장 핵심인 본문 영역을 흔들림 없이 즉시 보여줄 수 있을까? 라는 고민에서 개선을 시작했습니다.
기존의 방식은 상세 페이지의 주요 데이터를 클라이언트 컴포넌트에서 가져왔습니다. 하지만 Tanstack Query를 클라이언트에서만 사용할경우 필연적으로 다음과 같은 흐름을 겪게 됩니다.
'use client'의 제약: useQuery는 클라이언트 훅이므로 반드시 클라이언트 컴포넌트 내에 위치해야 합니다.레이아웃 시프트(Layout Shift)가 발생합니다.결과적으로 사용자는 페이지가 아직 다 뜨지 않았다는 불안정한 인상을 받게 되며, 이는 서비스 품질에 대한 신뢰도 저하로 이어집니다. 특히 SEO가 중요한 블로그 페이지에서 메타데이터와 핵심 콘텐츠가 서버 단계에서 준비되지 않았다는 점은 큰 약점이었습니다.
<div id="root"></div> 만 먼저 내려받고, JS가 실행될 때까지 기다리지만, SSR은 브라우저가 즉시 레이아웃을 게산할 수 있는 HTML을 전달받습니다.RSC Payload 형태로 전달합니다.

React Flight Protocol이라는 전용 스트리밍 프로토콜로 직렬화합니다. 이 Payload에는 JSX 결과물, 클라이언트 컴포넌트의 위치(Placeholder), 서버에서 넘겨주는 Props 정보가 담깁니다.RSC는 성능 면에서 뛰어나지만, 데이터를 다루는 세부적인 제어(캐싱, 재시도, 선언적 상태 관리 등)를 위해 이미 익숙한 Tanstack Query의 기능을 포기하고 싶지는 않았습니다.
기존 클라이언트 데이터 계층을 유지하면서 서버의 이점을 극대화하기 위해 RSC와 HydrationBoundary를 조합하는 방식을 선택했습니다.
이 방식의 핵심 시나리오는 서버에서 먼저 조회하고, 클라이언트에서는 이미 캐싱된 데이터를 그대로 활용한다입니다.
실제 구현 방식 서버 컴포넌트인 page.tsx에서 데이터를 미리 fetch하고 queryClient를 통해 데이터를 주입합니다.
1// page.tsx (Server Component) 2const Page = async ({ params }: PageProps<"id">) => { 3 const queryClient = getQueryClient(); 4 const blogId = Number(params.id); 5 6 // 서버에서 데이터 직접 조회 7 const { data } = await useGetDetail(blogId); 8 9 // 서버 측 Query Cache에 데이터 주입 10 queryClient.setQueryData(QUERY_KEYS.BLOG.DETAIL(blogId), data); 11 12 // 캐시 상태를 직렬화(dehydrate) 13 const dehydratedState = dehydrate(queryClient); 14 15 return ( 16 <HydrationBoundary state={dehydratedState}> 17 <Detail blogId={blogId} /> 18 </HydrationBoundary> 19 ); 20};
이렇게 하면 클라이언트 컴포넌트 내의 useQuery는 첫 실행 시 서버에서 미리 채워놓은 캐싱된 데이터를 이용하여 isLoading 상태를 거치지 않고 즉시 데이터를 화면에 그릴 수 있습니다.
이번 개선을 통해 다음과 같은 LCP 개선을 이룰 수 있었습니다.
단순히 수치상의 개선뿐만 아니라, 실제 체감 속도와 상호작용 반응성이 모두 향상되었으며 이는 곧 UX품질의 향상으로 이어졌습니다. 앞으로도 이러한 성능 최적화 방향을 유지하며 사용자 입장에서 더 빠르고 안정적인 웹 환경을 지속적으로 제공할 계획입니다.
RSC는 공짜가 아니다. 성능과 비용 사이의 선택 RSC 방식은 서버 사이드에서 컴포넌트 단위로 React를 실행하고, 이를 직렬화하여 클라이언트로 전달하기 때문에 일반 CSR 또는 SSR보다 서버 리소스를 상대적으로 더 많이 소모합니다.
특히 RSC는 요청이 발생할 때마다 서버에서 렌더링 로직이 수행되며, 서버에서의 CPU 연산 및 네트워크 I/O 부하가 증가하게 됩니다. 트래픽이 높은 서비스에서는 이러한 부하가 누적되면 서버 스케일링 비용과 응답 지연(latency)문제로 이어질 수 있습니다.
따라서 모든 페이지에 RSC를 일괄 적용하는 것은 비효율적이라고 판단했습니다.
RSC의 주요 장점은 초기 렌더링 속도 개선과 JS 번ㄷ늘 사이즈 감소에 있지만, 모든 페이지가 이 최적화의 우선순위를 필요로 하는 것은 아닙니다. 예를 들어 사용자 진입 빈도가 낮거나, 서버 데이터 의존도가 낮은 정적 페이지는 기존 SSC(Static Site Generation)나 ISR, CSR만으로도 충분히 좋은 성능을 낼 수 있습니다.
이러한 이유로, 서비스 트래픽 데이터를 기반으로 개인 프로젝트 뿐 아니라 실무 코드에서도 선택적으로 적용해볼 수 있는 전략을 개인적으로 수립해볼 수 있었습니다.
구체적으로는 아래와 같은 방식으로 진행해볼 생각입니다.
요약하자면, RSC는 분명히 성능과 사용자 경험 측면에서 강력한 장점을 제공합니다.
그러나 그만큼 서버 자원 소비와 인프라 비용도 함께 늘어나기 때문에, 모든 페이지가 아닌 "렌더링 품질이 직접적인 UX 지표에 영향을 주는 주요 페이지" 중심으로 선택적 적용하는 전략이 가장 현실적이라고 판단했습니다.
이런 접근을 통해 RSC의 장점을 효율적으로 활용하면서도 서비스 안정성과 비용 효율성을 동시에 확보할 수 있을 것입니다.