본문 바로가기
Frontend2026년 5월 7일7분 읽기

RSC 데이터 페칭 4패턴 — Next.js 16에서 무엇을 언제 쓰나

YS
김영삼
조회 273
RSC 데이터 페칭 4패턴 — Next.js 16에서 무엇을 언제 쓰나

핵심 요약

RSC가 안정화되면서 데이터 페칭 패턴이 4가지로 정리됐다. 각각 적합한 상황이 다른데, 잘못 섞으면 워터폴·하이드레이션 문제로 페이지가 멈춘다. Next.js 16 기준 실전 정리.

1. 패턴 A — 서버 컴포넌트에서 직접 await

가장 흔한 패턴. 페이지 로드 시 한 번 필요한 데이터.

// app/products/[id]/page.tsx — Server Component
async function ProductPage({ params }) {
  const { id } = await params
  const product = await db.product.findUnique({ where: { id } })
  return <ProductDetail product={product} />
}

장점: 클라이언트 JS 0줄. SEO 친화. 단점: 모든 데이터 도착할 때까지 페이지 안 보임.

2. 패턴 B — Suspense + use() 스트리밍

일부는 빨리 보이고 일부는 늦게 와도 되는 경우.

// 서버에서 promise만 만들고 await 안 함
async function ProductPage({ params }) {
  const { id } = await params
  const productPromise = db.product.findUnique({ where: { id } })
  const reviewsPromise = db.review.findMany({ where: { productId: id } })

  return (
    <>
      <Suspense fallback={<Skeleton />}>
        <Product promise={productPromise} />
      </Suspense>
      <Suspense fallback={<ReviewSkeleton />}>
        <Reviews promise={reviewsPromise} />
      </Suspense>
    </>
  )
}

'use client'
import { use } from 'react'
function Product({ promise }) {
  const product = use(promise)
  return <h1>{product.name}</h1>
}

핵심: promise를 await 없이 클라이언트로 넘기고 use()로 풀어준다. 핫 데이터는 먼저 그리고 느린 데이터는 streaming.

3. 패턴 C — Server Action 뮤테이션

폼 제출·버튼 클릭으로 데이터 변경.

// app/actions/posts.ts
'use server'
export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  await db.post.create({ data: { title } })
  revalidateTag('posts')
}

// 사용
<form action={createPost}>
  <input name="title" />
  <button>저장</button>
</form>

장점: API 라우트 안 만들어도 됨. 단점: 진행률·낙관적 업데이트는 별도 처리.

4. 패턴 D — 클라이언트 페칭 (SWR/React Query)

실시간성 강한 위젯·정기 폴링·무한 스크롤.

'use client'
import useSWR from 'swr'
function LiveStock({ symbol }) {
  const { data } = useSWR(`/api/stock/${symbol}`, fetcher, {
    refreshInterval: 1000,
  })
  return <span>{data?.price}</span>
}

5. 결정 트리

상황패턴
SEO 중요·페이지 로드 시 1회A (서버 await)
한 페이지에 빠른+느린 데이터 섞임B (Suspense + use)
폼·버튼으로 변경C (Server Action)
1초마다 갱신·실시간성D (클라이언트 페칭)
무한 스크롤D + 초기 데이터는 A
사용자별 다른 데이터, 캐시 의미 없음A 또는 D

6. 워터폴 피하기

안티패턴: 의존 없는 두 쿼리를 순차로 await.

// ❌
const user = await getUser(id)
const posts = await getPosts(id)  // user에 의존 안 함

// ✅
const [user, posts] = await Promise.all([
  getUser(id),
  getPosts(id),
])

// ✅✅ — 둘 다 streaming
const userPromise = getUser(id)
const postsPromise = getPosts(id)
return <Suspense...>...</Suspense>

7. fetch + 16의 캐시 변화 주의

Next.js 16부터 fetch는 기본 동적이다(이전 글 참조). 캐시 원하면 명시.

const data = await fetch(url, {
  cache: 'force-cache',
  next: { revalidate: 60, tags: ['products'] },
})

8. 흔한 함정

  • 서버 컴포넌트에 useState/useEffect: 불가. 'use client' 분리 필수.
  • Server Action에서 redirect 후 finally: 동작 안 함. redirect는 throw로 흐름 끊김.
  • use(promise)를 매 렌더링 새로 생성된 promise에: 무한 루프. 부모에서 만들어 내려야 함.
  • client 컴포넌트가 server 컴포넌트를 children으로 받음: 가능. import는 불가, props로만.

9. 실측 — 같은 페이지 4패턴 비교

상품 상세 페이지(상품+리뷰+추천 3섹션)을 4가지로 구현해 비교.

패턴TTFBFCPLCPJS
A (서버 await 전체)180ms800ms820ms0kB
B (스트리밍)40ms180ms820ms2kB
D (클라이언트 페칭)30ms110ms1,200ms34kB

B 패턴이 균형 최적. D는 LCP가 늦지만 사용자 인터랙션 빠름.

참고

  • react.dev/reference/react/use
  • nextjs.org/docs/app — data fetching

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록