Post
Next.js 서버 컴포넌트(Server Components) 완벽 이해하기
왜 이 주제가 중요한가?
Next.js 13 이후 서버 컴포넌트는 React의 패러다임을 바꿨습니다. 실제 프로젝트에서 성능, 보안, 번들 크기를 동시에 개선할 수 있는 핵심 기술입니다.
클라이언트에서 처리하던 데이터 페칭과 렌더링을 서버로 옮기면서 불필요한 JavaScript를 줄일 수 있습니다. 이는 초기 로딩 속도와 사용자 경험을 크게 향상시킵니다.
핵심 개념
-
서버 컴포넌트(Server Component) 기본적으로 서버에서만 실행되며 클라이언트에 JavaScript를 보내지 않습니다. 데이터베이스 접근, API 키 보호, 대용량 라이브러리 사용에 최적화되어 있습니다.
-
클라이언트 컴포넌트(Client Component) ‘use client’ 지시어로 명시하며 브라우저에서 실행됩니다. 상태 관리, 이벤트 리스너, 훅(useState, useEffect 등)이 필요한 경우 사용합니다.
-
컴포넌트 경계(Component Boundary) 서버와 클라이언트 컴포넌트는 명확히 분리되어야 합니다. 서버 컴포넌트는 클라이언트 컴포넌트를 자식으로 가질 수 있지만, 역은 불가능합니다.
-
데이터 페칭 패턴 서버 컴포넌트에서 직접 async/await로 데이터를 가져옵니다. 클라이언트 컴포넌트는 useEffect나 라이브러리(SWR, React Query)를 사용합니다.
-
점진적 마이그레이션 기존 프로젝트에서 모든 컴포넌트를 한 번에 바꿀 필요 없습니다. 필요한 부분부터 서버 컴포넌트로 전환할 수 있습니다.
실습 예제
1단계: 기본 서버 컴포넌트 작성
// app/posts/page.tsx - 서버 컴포넌트 (기본값)
async function PostsList() {
const response = await fetch('https://api.example.com/posts', {
cache: 'revalidate',
next: { revalidate: 3600 }
});
const posts = await response.json();
return (
<div>
<h1>게시물 목록</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
export default PostsList;
2단계: 클라이언트 컴포넌트 추가
// app/components/PostFilter.tsx
'use client';
import { useState } from 'react';
export function PostFilter() {
const [filter, setFilter] = useState('');
return (
<div>
<input
type="text"
placeholder="검색..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<p>현재 필터: {filter}</p>
</div>
);
}
3단계: 서버와 클라이언트 조합
// app/posts/page.tsx - 수정된 버전
import { PostFilter } from '@/app/components/PostFilter';
async function PostsList() {
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
return (
<div>
<h1>게시물 목록</h1>
<PostFilter />
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
export default PostsList;
이 예제에서 PostsList는 서버에서 데이터를 가져오고, PostFilter는 클라이언트에서 상태를 관리합니다.
흔한 실수
-
서버 컴포넌트에서 훅 사용 useState, useEffect, useContext 등은 클라이언트 컴포넌트에서만 사용 가능합니다. ‘use client’를 빼먹으면 런타임 에러가 발생합니다.
-
클라이언트 컴포넌트에서 서버 함수 직접 호출 서버 전용 함수(데이터베이스 접근 등)를 클라이언트 컴포넌트에서 직접 사용할 수 없습니다. API 라우트나 Server Action을 거쳐야 합니다.
-
Props로 직렬화 불가능한 객체 전달 서버 컴포넌트에서 클라이언트 컴포넌트로 함수나 Date 객체를 직접 전달하면 에러가 발생합니다. 직렬화 가능한 데이터만 전달해야 합니다.
-
과도한 서버 컴포넌트 중첩 서버 컴포넌트가 많을수록 초기 렌더링 시간이 늘어날 수 있습니다. 필요한 부분만 서버 컴포넌트로 유지하세요.
-
캐싱 전략 무시 fetch 옵션에서 cache와 revalidate를 설정하지 않으면 예상치 못한 동작이 발생할 수 있습니다.
오늘의 실습 체크리스트
- Next.js 프로젝트에서 app 디렉토리 구조 확인하기
- 기본 서버 컴포넌트 작성하고 데이터 페칭 테스트하기
- ‘use client’ 지시어를 사용한 클라이언트 컴포넌트 작성하기
- 서버 컴포넌트에서 클라이언트 컴포넌트로 props 전달해보기
- 브라우저 DevTools에서 번들 크기 비교 (서버 vs 클라이언트 컴포넌트)
- 하나의 페이지에서 서버와 클라이언트 컴포넌트를 함께 사용하는 예제 구현하기
댓글