Next.js 15 앱에서 React Server Components 마스터하기
요약
Next.js 15에서 React Server Components(RSC)를 효과적으로 활용하기 위한 실무 패턴을 다룹니다. 서버와 클라이언트 컴포넌트의 역할 분리, 데이터 페칭 전략, 폴더 구조 설계 등 성능 최적화와 DX 향상을 위한 구체적인 가이드를 제공합니다.
핵심 포인트
- RSC를 통한 클라이언트 번들 크기 감소 및 초기 로딩 속도 향상
- 서버 컴포넌트에서 데이터베이스 및 API 직접 호출 가능
- 서버와 클라이언트 컴포넌트 간의 명확한 역할 분리 및 멘탈 모델 구축
- Next.js 15 App Router 환경에서의 실무적인 아키텍처 설계 방법
Next.js 15 앱에서 React Server Components 마스터하기
React Server Components (RSC)는 "실험적인 호기심" 단계에서 벗어나, 특히 Next.js 앱에서 2026년을 정의하는 주요 프론트엔드 트렌드 중 하나로 자리 잡았습니다. 많은 팀이 새로운 렌더링 규칙, 데이터 페칭 (Data-fetching) 패턴, 그리고 서버와 클라이언트의 관심사를 분리하는 데 따른 정신적 부하를 조율하며 마이그레이션(Migration)을 진행 중입니다. 그 결과: 많은 혼란과 약간의 카고 컬트 (Cargo cult), 그리고 문제가 발생했을 때의 고통스러운 리팩토링 (Refactor)이 뒤따르고 있습니다.
이 포스트에서는 TypeScript를 사용하여 React Server Components로 실제 Next.js 15 앱을 구축하기 위한 실용적이고 검증된 패턴을 살펴봅니다. 폴더 구조를 어떻게 잡을지, 어떤 컴포넌트를 어디에 둘지 결정하는 방법, 데이터 페칭 (Data fetching) 처리, 마이크로 프론트엔드 (Micro frontends) 연결, 그리고 테스트와 DX (Developer Experience)를 제어하는 방법을 배우게 될 것입니다. 이 글을 다 읽을 때쯤이면, 이번 주에 바로 기존 앱에 적용할 수 있는 명확한 청사진을 갖게 될 것입니다.
2026년에 React Server Components가 중요한 이유
React Server Components는 데이터베이스나 파일 시스템과 같은 서버 전용 리소스에 접근하면서 컴포넌트를 서버에서 렌더링할 수 있게 해주며, 클라이언트에는 최소한의 페이로드 (Payload)만 전송합니다. 이는 번들 크기 (Bundle size)를 줄이고, 특히 복잡한 대시보드나 데이터 집약적인 뷰에서 초기 페이지 로드 속도를 향상시킵니다.
Next.js 15는 App Router와 Server Components를 페이지와 레이아웃을 구축하는 기본 방식으로 설정함으로써 RSC를 강력하게 밀어붙이고 있습니다. 만약 여전히 모든 것을 클라이언트 컴포넌트 (Client component)로 취급하고 있다면, 성능과 DX (Developer Experience)의 이점을 놓치고 있는 것입니다.
높은 수준에서 RSC는 다음과 같은 도움을 줍니다:
- 순수 렌더링과 데이터 페칭 (Data fetching)이 서버로 이동하기 때문에 더 작은 클라이언트 번들 (Client bundles).
- 커스텀 클라이언트 측 페치 래퍼 (Client-side fetch wrappers)를 구축할 필요 없이 서버 컴포넌트에서 데이터베이스나 API 코드를 직접 호출할 수 있으므로 더 단순한 데이터 접근.
- 더 나은 엣지 (Edge) 정렬: 서버 컴포넌트를 사용하여 엣지에서 렌더링하는 것은 지연 시간 (Latency)과 지역성 (Locality)에 집중하는 현대적인 배포 플랫폼과 잘 맞습니다.
하지만 이러한 이점은 명확한 경계를 중심으로 아키텍처를 설계할 때만 얻을 수 있습니다. 이제 시작해 봅시다.
멘탈 모델 (Mental Model): 서버 컴포넌트 (Server Components) vs 클라이언트 컴포넌트 (Client Components)
명확한 멘탈 모델 (Mental Model)이 없다면 RSC (React Server Components)를 효과적으로 사용할 수 없습니다. Next.js 문서는 다음과 같이 간단하게 구분합니다: 서버 컴포넌트는 서버에서만 실행되며, 클라이언트 컴포넌트는 브라우저에서 실행되며 상호작용 (interactivity)을 처리합니다.
실무적으로는 다음과 같이 생각하면 됩니다:
- 서버 컴포넌트 (Server components): 데이터 로딩 (data-loading), 컴포지션 (composition), 그리고 무거운 마크업 (heavy markup). 브라우저 전용 API를 사용할 수 없으며,
useState나useEffect와 같은 훅 (hooks)을 사용할 수 없습니다. - 클라이언트 컴포넌트 (Client components): 상호작용 (interactivity), 상태가 있는 UI (stateful UI), 이벤트 핸들러 (event handlers), 브라우저 API, 애니메이션 (animations), 복잡한 폼 (complex forms).
유용한 경험칙 (rule of thumb):
서버 컴포넌트는 **무엇을 보여줄 것인가 (what to show)**를 담당합니다. 클라이언트 컴포넌트는 **어떻게 동작할 것인가 (how it behaves)**를 담당합니다.
예시: 대시보드 페이지 분리하기
// app/dashboard/page.tsx
// 서버 컴포넌트 (Next.js App Router의 기본값)
import { fetchOrders } from '@/lib/data';
...
이 설정에서:
DashboardPage는 데이터 로딩과 레이아웃 (layout)을 담당하며 서버 컴포넌트로 유지됩니다.OrdersTable은'use client'를 통해 명시적으로 클라이언트 컴포넌트로 표시되며, 상호작용 동작에 집중합니다.
이러한 경계를 염두에 둔다면, RSC 도입은 거대한 빅뱅 식의 재작성 (big-bang rewrite)이 아니라 작고 예측 가능한 일련의 리팩터링 (refactors) 과정이 됩니다.
React Server Components를 위한 스펙 기반 아키텍처 (Spec-Driven Architecture)
스키마 기반 (Schema-driven) 또는 스펙 기반 (spec-driven) 개발은 RSC와 자연스럽게 어우러집니다. 서버 컴포넌트는 강력한 타입 (strongly typed)을 가질 수 있고 도메인 로직 (domain logic)에 가깝기 때문입니다. RSC가 인기를 얻으면서 고품질 가이드들은 성능 (performance)과 번들링 (bundling)을 강조하지만, 일반적으로 아키텍처 모델링 (architectural modeling) 측면은 경시하는 경향이 있습니다. 그것이 바로 여러분이 공략할 수 있는 격차 (gap)입니다.
간단한 패턴:
- 공유된 TypeScript 모듈에서 도메인 타입 (domain types)과 API 계약 (API contracts)을 정의합니다.
- 데이터 페칭 함수 (data-fetching functions, 리포지토리 (repositories), 서비스 (services))를
lib/또는data/에 유지합니다. - 서버 컴포넌트가 이러한 호출을 오케스트레이션 (orchestrating)하고 클라이언트 컴포넌트에 타입이 지정된 프롭스 (typed props)를 전달하는 책임을 갖도록 합니다.
예시: 스펙 기반 데이터 레이어 (Spec-Driven Data Layer)
// lib/types.ts
export type Order = {
id: string;
...
주요 장점:
- 도메인 명세(lib/types.ts)와 UI 간의 강제된 분리.
- 명확한 소유권: 서버 컴포넌트(Server Components)는 명세에 부합하는 데이터 호출을 조율하며, 클라이언트 컴포넌트(Client Components)는 props를 단순하고 예측 가능하게 유지합니다.
- 계약(Contracts)이 타입화되어 중앙 집중화되어 있으므로 마이크로 프론트엔드(Micro Frontends) 또는 기타 서비스 간의 재사용이 용이합니다.
비동기 서버 컴포넌트(Async Server Components)를 활용한 데이터 페칭(Data Fetching) 및 캐싱(Caching) 패턴
최신 RSC 가이드는 워터폴(Waterfalls) 현상과 비대해진 클라이언트 번들(Client Bundles)을 방지하기 위해, 데이터 페칭(Data Fetching)을 서버 컴포넌트에서 수행하는 것이 가장 좋다고 강조합니다. Next.js는 비동기 서버 컴포넌트와 내장된 캐싱(Caching) 동작을 허용함으로써 이를 수용합니다.
패턴 1: 비동기 페이지 컴포넌트 (Async Page Components)
비동기 페이지 컴포넌트만으로도 충분한 경우가 많습니다:
// app/products/page.tsx
import { fetchProducts } from '@/lib/data';
import { ProductsGrid } from './ProductsGrid';
...
ProductsPage가 서버에서 렌더링될 때:
- 요청당 한 번
fetchProducts를 호출합니다 (캐싱 적용 대상). - 결과는 직렬화(Serialized)되어 RSC 페이로드(Payload)의 일부로 클라이언트에 스트리밍(Streamed)됩니다.
패턴 2: 자식 서버 컴포넌트에 데이터 호출 공동 배치 (Co-locating Data Calls in Child Server Components)
모든 것을 최상위 레벨에서 페칭하는 대신, 데이터 페칭을 서버 자식 컴포넌트로 내려보내 각 부분을 집중할 수 있습니다:
// app/account/page.tsx
import { AccountOverview } from './AccountOverview';
import { RecentActivity } from './RecentActivity';
...
이를 통해 각 서버 컴포넌트는 단일 데이터 관심사에 집중할 수 있으며, 이는 명세 기반 계약(Spec-driven contracts)과 잘 매핑되어 리팩터링(Refactor)을 더 안전하게 만듭니다.
패턴 3: 서버 컴포넌트 + 클라이언트 훅 (Server Components + Client Hooks)
매우 상호작용이 많은 뷰의 경우, 서버에서 프리페치(Prefetch)된 데이터와 클라이언트 훅(Client Hooks)을 결합할 수 있습니다:
// app/search/SearchResults.tsx
'use client';
...
균형점: 서버 컴포넌트는 초기 데이터를 페칭하고, 클라이언트 컴포넌트는 점진적인 업데이트와 상호작용을 관리합니다.
마이크로 프론트엔드 아키텍처(Micro Frontend Architectures)에서 React Server Components 사용하기
트렌드 기사들은 "에지 인식 아키텍처 (edge-aware architectures)"와 분산된 프론트엔드 소유권(distributed frontend ownership)을 2026년의 핵심 기술로 강조하고 있습니다. 만약 마이크로 프론트엔드를 운영 중이라면, 계약(contracts)을 신중하게 설계할 경우 RSC는 경계를 복잡하게 만드는 대신 오히려 단순화할 수 있습니다.
실질적인 접근 방식:
- 각 마이크로 프론트엔드를 자체적인 RSC 엔트리 포인트(entry points)를 가진 수직 슬라이스(vertical slice)로 취급합니다.
- 공유 도메인(shared domains)과 타입(types)은 공통 라이브러리(내부 패키지 또는 워크스페이스)에 둡니다.
- 각 슬라이스는 쉘 앱(shell app)에 의해 조합될 수 있는 하나 이상의 서버 컴포넌트(server components)를 노출합니다.
예시: 서로 다른 마이크로 프론트엔드로부터 서버 컴포넌트 조합하기
'orders(주문)'와 'billing(결제)'이라는 두 개의 마이크로 프론트엔드가 있다고 가정해 봅시다.
// app/app-shell/page.tsx
import { OrdersPanel } from 'mf-orders/OrdersPanel';
import { BillingPanel } from 'mf-billing/BillingPanel';
...
서버 컴포넌트는 서버 전용 코드(server-only code)를 직접 호출할 수 있기 때문에, 각 마이크로 프론트엔드는 구현 세부 사항을 쉘(shell)로 유출하지 않고 자체적인 데이터 레이어(data layer)를 관리할 수 있습니다. 타입이 지정된 계약(Typed contracts)은 여전히 모든 것을 조합 가능하고 안전하게 유지해 줍니다.
주요 위험 요소: 공유 클라이언트 컴포넌트(shared client components)를 통한 과도한 결합(over-coupling). 공유 UI는 대부분 클라이언트 측에 두되 가볍게 유지하고, 비즈니스 특정 위젯(business-specific widgets)보다는 도메인 불가지론적 프리미티브(domain-agnostic primitives, 예: 버튼, 레이아웃)를 선호하십시오.
RSC를 활용한 테스트 및 개발자 경험 (Developer Experience)
RSC 채택이 증가함에 따라, 커뮤니티의 논의는 서버와 클라이언트 컴포넌트를 가로지르는 흐름을 어떻게 테스트하고, 디버깅하며, 추론할 것인가와 같은 'day-two'의 현실에 점점 더 집중하고 있습니다.
좋은 소식은, 몇 가지 조정만 거치면 기존 테스트 도구의 대부분을 재사용할 수 있다는 점입니다.
서버 컴포넌트 테스트하기
서버 컴포넌트는 대부분 props와 환경(environment)으로부터 JSX 출력으로 이어지는 순수 함수(pure functions)입니다. 이는 테스트하기에 매우 좋습니다.
옵션:
- 데이터 레이어 단위 테스트 (Unit test the data layer):
fetchOrders,fetchAccountOverview등을 Jest를 사용하여 모의 객체(mocks) 또는 테스트 데이터베이스를 대상으로 테스트합니다. - 최소한의 하네스(harness)로 서버 컴포넌트 렌더링: 테스트 프레임워크를 사용하여 컴포넌트의 출력을 문자열로 렌더링하고 HTML을 검증(assert)하거나, 데이터와 마크업(markup)을 아우르는 통합 테스트(integration tests)로 취급합니다.
데이터 레이어 테스트 예시:
// lib/__tests__/data.test.ts
import { fetchOrders } from '../data';
...
여기서 당신은 RSC 메커니즘 자체를 테스트하는 것이 아니라, 계약(contract)과 동작(behavior)을 테스트하고 있는 것입니다.
클라이언트 컴포넌트 테스트 (Testing Client Components)
클라이언트 컴포넌트는 여전히 Jest, React Testing Library, Cypress, Playwright 등 평소 사용하던 스택을 사용합니다. 이러한 테스트는 다음 사항에 집중하세요:
- 상호작용의 정확성 (클릭, 폼 제출, 키보드 이벤트).
- 접근성(Accessibility), ARIA 사용 및 반응형 동작.
예시:
// app/dashboard/__tests__/OrdersTable.test.tsx
import { render, screen } from '@testing-library/react';
import { OrdersTable } from '../OrdersTable';
...
RSC가 로직을 분리했기 때문에 테스트는 더욱 집중될 수 있습니다:
- 데이터 및 계약: 서버 측 테스트.
- 동작 및 UX: 클라이언트 측 테스트.
명세 기반 타입(spec-driven types)과 결합하면, 특이한 RSC 전용 도구 없이도 훌륭한 테스트 커버리지를 확보할 수 있습니다.
핵심 요약 (Key Takeaways)
- React Server Components는 Next.js 15의 주요 기능으로, 올바르게 사용하면 더 작은 번들 크기와 더 빠른 초기 로딩 속도를 제공합니다.
- 데이터와 구성을 위한 서버, 상호작용을 위한 클라이언트라는 명확한 사고의 경계(mental boundary)를 설정하면 RSC 도입을 관리 가능하고 예측 가능하게 만들 수 있습니다.
- 명세 기반 아키텍처(Spec-driven architecture)는 RSC와 자연스럽게 어우러집니다. 타입을 중앙 집중화하고 계약을 정의하며, 서버 컴포넌트를 데이터 근처에 두고 클라이언트 컴포넌트에는 단순한 props를 전달하세요.
- 마이크로 프론트엔드(Micro frontends)는 공유 계약이 타입화되어 있고 안정적으로 유지되는 한, 서로 다른 슬라이스(slices)로부터 서버 컴포넌트를 깔끔하게 구성할 수 있습니다.
- 테스트 방식은 익숙하게 유지됩니다. 데이터 레이어는 단위 테스트를 수행하고 서버 컴포넌트는 신중하게 렌더링하며, 클라이언트 상호작용 및 엔드 투 엔드(end-to-end) 흐름에는 표준 도구를 계속 사용하세요.
결론 (Conclusion)
React Server Components (RSC)는 단순히 반짝이는 새로운 기능이 아닙니다. 특히 에지(edge) 컴퓨팅과 성능이 2026년의 기본 기대치가 됨에 따라, 이는 Next.js 앱이 구축되고 배포되는 방식의 구조적 변화를 의미합니다. RSC를 명세 기반 아키텍처(spec-driven architecture)와 서버 및 클라이언트 책임 간의 절제된 분리(disciplined split)와 결합하면, 추론하기 더 쉽고, 배포 비용이 저렴하며, 변화에 더 탄력적인(resilient) 앱을 만들 수 있습니다. 단일 페이지를 마이그레이션하는 것부터 시작하여, 계약(contracts)을 정의하고, 데이터 로딩을 서버 컴포넌트로 밀어 넣으세요. 그런 다음, 반복(iterate)하십시오.
이번 주에 여러분의 코드베이스에서 서버 주도형(server-driven)이자 명세 기반(spec-backed) 컴포넌트로 리팩터링하고 싶은 첫 번째 기능은 무엇인가요?
태그: nextjs, react, typescript, react-server-components, frontend-architecture, micro-frontends, performance, testing
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기