Next.js App Router: 서버 컴포넌트와 클라이언트 컴포넌트 제대로 나누기
데브노트 편집팀·2026.07.08·7분 읽기
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는 빠르고 단순합니다.
ADVERTISEMENT
함께 보면 좋은 글
프론트엔드· 6분
CSS Flexbox vs Grid: 언제 무엇을 써야 할까
Flexbox와 Grid는 경쟁이 아니라 역할이 다릅니다. 1차원이면 Flex, 2차원이면 Grid라는 원칙과 실제 레이아웃 예제로 선택 기준을 명확히 정리합니다.
2026.07.07
프론트엔드· 7분
TypeScript 제네릭 완전 정복: 실무에서 바로 쓰는 패턴
제네릭이 어렵게 느껴지는 건 '왜 쓰는지'를 모르기 때문입니다. 타입 안전성을 지키면서 재사용성을 높이는 제네릭의 핵심 문법과 extends·keyof·유틸리티 타입까지 실전 예제로 정리합니다.
2026.07.06
프론트엔드· 6분
TypeScript 입문: 타입 기초부터 인터페이스, 제네릭까지
자바스크립트 개발자를 위한 TypeScript 입문 가이드입니다. 기본 타입, 인터페이스, 유니온, 제네릭까지 꼭 필요한 핵심 개념을 실전 코드로 빠르게 익혀보세요.
2026.07.01