💻데브노트소개
🎨

Next.js App Router: 서버 컴포넌트와 클라이언트 컴포넌트 제대로 나누기

데브노트 편집팀·2026.07.08·7분 읽기
X(트위터)
ADVERTISEMENT

Next.js App Router의 핵심은 모든 컴포넌트가 기본적으로 서버 컴포넌트라는 점입니다. 필요할 때만 'use client'로 클라이언트 컴포넌트로 전환합니다. 이 경계를 이해하면 App Router의 80%는 끝납니다.

서버 컴포넌트가 기본인 이유

서버 컴포넌트는 서버에서 렌더링되어 HTML만 전송합니다. JS 번들에 포함되지 않으므로 더 가볍고, DB·파일·비밀키에 직접 접근할 수 있습니다.

// app/posts/page.tsx — 서버 컴포넌트(기본)
async function PostsPage() {
  const posts = await db.post.findMany(); // DB 직접 호출 OK
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

async 함수 컴포넌트로 만들어 await로 바로 데이터를 가져올 수 있습니다. useEffect 패칭이 필요 없습니다.

'use client'가 필요한 순간

다음 중 하나라도 쓰면 클라이언트 컴포넌트여야 합니다.

  • useState, useEffect 등 React 훅
  • onClick, onChange 등 이벤트 핸들러
  • 브라우저 API(window, localStorage)
'use client';
import { useState } from 'react';

export function LikeButton() {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(!liked)}>{liked ? '♥' : '♡'}</button>;
}

올바른 경계 설계

두는 곳내용
서버 컴포넌트데이터 패칭, 레이아웃, 정적 콘텐츠
클라이언트 컴포넌트상호작용(버튼·폼·토글)

핵심 원칙: 클라이언트 컴포넌트는 트리의 잎(leaf)에 작게 두세요. 페이지 최상단을 통째로 'use client'로 만들면 서버 컴포넌트의 이점이 사라집니다.

// 좋은 패턴: 서버 페이지 안에 작은 클라이언트 섬을 심기
export default async function Page() {
  const data = await getData();         // 서버
  return <article>{data.text}<LikeButton /></article>; // 잎만 클라이언트
}

흔한 실수

  • 서버 컴포넌트에 onClick 넣기 → 에러
  • 클라이언트 컴포넌트에서 DB 직접 호출 → 비밀키 노출 위험
  • 데이터를 클라이언트에서 useEffect로 다시 패칭 → 불필요

마무리 체크리스트

  • 기본은 서버 컴포넌트, 필요할 때만 'use client'
  • 데이터 패칭은 서버에서 async/await
  • 상호작용 컴포넌트는 작게, 잎에 배치
  • 비밀키·DB 접근은 서버에서만

경계만 잘 그으면 App Router는 빠르고 단순합니다.

#Next.js#App Router#서버컴포넌트
X(트위터)
ADVERTISEMENT