Next.js App Router를 학습할 때 가장 먼저 이해해야 하는 개념 중 하나가 Server Component와 Client Component입니다. 두 컴포넌트는 모두 React 컴포넌트처럼 보이지만, 실행되는 위치와 사용할 수 있는 기능이 다릅니다. 이 차이를 제대로 이해하지 못하면 데이터 조회, 이벤트 처리, 상태 관리, 성능 최적화에서 자주 혼란을 겪게 됩니다.
Server Component란?
Server Component는 이름 그대로 서버에서 실행되는 컴포넌트입니다. Next.js App Router에서는 기본적으로 모든 컴포넌트가 Server Component로 동작합니다. 즉, 파일 상단에 별도의 설정을 하지 않으면 해당 컴포넌트는 서버에서 렌더링됩니다.
Server Component의 가장 큰 장점은 서버에서 직접 데이터를 가져올 수 있다는 점입니다. 예를 들어 데이터베이스 조회, 외부 API 호출, 파일 시스템 접근 같은 작업을 컴포넌트 내부에서 바로 처리할 수 있습니다.
export default async function ProductList() {
const products = await fetch("https://example.com/api/products").then(res =>
res.json()
);
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
위 코드는 서버에서 실행되기 때문에 브라우저에 불필요한 JavaScript를 보내지 않습니다. 사용자는 이미 완성된 HTML에 가까운 결과를 받게 되고, 초기 로딩 성능에도 유리합니다.
또한 Server Component에서는 민감한 정보를 안전하게 다룰 수 있습니다. 데이터베이스 접속 정보, API Secret Key, 서버 전용 로직 등을 클라이언트에 노출하지 않고 사용할 수 있습니다. 그래서 게시글 목록, 상품 상세, 사용자 정보 조회처럼 “화면에 보여줄 데이터를 서버에서 가져오는 역할”에 적합합니다.
다만 Server Component에는 제한도 있습니다. 브라우저에서만 동작하는 기능은 사용할 수 없습니다. 예를 들어 useState, useEffect, onClick, window, localStorage 같은 기능은 Server Component에서 사용할 수 없습니다. 이런 기능은 브라우저 환경이 필요하기 때문에 Client Component에서 처리해야 합니다.
Client Component란?
Client Component는 브라우저에서 실행되는 컴포넌트입니다. 사용자의 클릭, 입력, 상태 변경처럼 상호작용이 필요한 UI를 만들 때 사용합니다. Client Component로 만들려면 파일 상단에 "use client"를 선언해야 합니다.
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
클릭 수: {count}
</button>
);
}
위 컴포넌트는 버튼 클릭에 따라 상태가 변경됩니다. 이런 동작은 서버에서 처리할 수 없기 때문에 Client Component가 필요합니다.
Client Component에서는 React의 상태 관리 Hook을 사용할 수 있습니다. 대표적으로 useState, useEffect, useRef, useReducer 등을 사용할 수 있고, 브라우저 API인 window, document, localStorage에도 접근할 수 있습니다. 모달, 드롭다운, 탭 메뉴, 검색 입력창, 좋아요 버튼, 폼 입력 처리 등은 Client Component로 만드는 경우가 많습니다.
하지만 Client Component를 많이 사용할수록 브라우저로 전달되는 JavaScript 양이 증가합니다. 이는 초기 로딩 속도와 성능에 영향을 줄 수 있습니다. 따라서 모든 컴포넌트를 Client Component로 만드는 것은 좋은 방식이 아닙니다. 꼭 상호작용이 필요한 부분만 Client Component로 분리하는 것이 좋습니다.
두 컴포넌트의 핵심 차이
Server Component와 Client Component의 가장 큰 차이는 실행 위치입니다. Server Component는 서버에서 실행되고, Client Component는 브라우저에서 실행됩니다.
Server Component는 데이터 조회와 정적인 화면 구성에 강합니다. 서버에서 데이터를 가져오고 HTML을 구성한 뒤 클라이언트로 전달하기 때문에 성능과 보안 측면에서 유리합니다. 반면 사용자 이벤트를 직접 처리할 수는 없습니다.
Client Component는 사용자와 상호작용하는 UI에 강합니다. 클릭, 입력, 애니메이션, 상태 변경처럼 브라우저에서 즉시 반응해야 하는 기능을 처리할 수 있습니다. 대신 클라이언트에 JavaScript가 전달되어야 하므로 Server Component보다 부담이 커질 수 있습니다.
정리하면 다음과 같습니다.
- 데이터 조회, 목록 출력, 상세 페이지 렌더링은 Server Component에 적합합니다.
- 버튼 클릭, 입력 폼, 모달, 탭 메뉴처럼 동작이 필요한 UI는 Client Component에 적합합니다.
- 기본은 Server Component로 작성하고, 필요한 부분만 Client Component로 분리하는 것이 좋습니다.
함께 사용하는 방식
실제 프로젝트에서는 Server Component와 Client Component를 함께 사용합니다. 예를 들어 상품 상세 페이지는 서버에서 상품 정보를 조회하고, 장바구니 버튼은 클라이언트에서 클릭 이벤트를 처리할 수 있습니다.
import AddToCartButton from "./AddToCartButton";
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<AddToCartButton productId={product.id} />
</main>
);
}
"use client";
export default function AddToCartButton({ productId }) {
const handleClick = () => {
alert(`${productId} 상품을 장바구니에 담았습니다.`);
};
return <button onClick={handleClick}>장바구니 담기</button>;
}
이 구조에서 ProductPage는 Server Component이고, AddToCartButton은 Client Component입니다. 상품 정보 조회는 서버에서 처리하고, 버튼 클릭은 브라우저에서 처리합니다. 이렇게 역할을 분리하면 성능과 사용자 경험을 모두 챙길 수 있습니다.
실무에서의 판단 기준
컴포넌트를 만들 때는 먼저 “이 컴포넌트가 브라우저에서 꼭 동작해야 하는가?”를 생각하면 됩니다. 만약 단순히 데이터를 보여주는 역할이라면 Server Component로 충분합니다. 반대로 사용자의 입력, 클릭, 상태 변경이 필요하다면 Client Component로 만들어야 합니다.
예를 들어 블로그 글 상세 페이지의 제목, 본문, 작성일은 Server Component로 처리하는 것이 적절합니다. 하지만 댓글 작성 폼, 좋아요 버튼, 공유 버튼은 Client Component가 적절합니다.
중요한 점은 "use client"를 너무 넓은 범위에 적용하지 않는 것입니다. 상위 컴포넌트에 "use client"를 선언하면 그 하위 컴포넌트들도 클라이언트 번들에 포함될 수 있습니다. 따라서 가능한 한 작은 단위의 컴포넌트에만 "use client"를 사용하는 것이 좋습니다.
Server Component와 Client Component는 어느 하나가 더 좋은 개념이 아니라, 역할이 다른 개념입니다. Server Component는 서버에서 데이터를 가져오고 빠르게 화면을 구성하는 데 적합합니다. Client Component는 사용자의 행동에 반응하는 동적인 UI를 만드는 데 적합합니다.
Next.js App Router에서는 기본적으로 Server Component를 사용하고, 상호작용이 필요한 부분만 Client Component로 분리하는 방식이 권장됩니다. 이 원칙을 이해하면 불필요한 JavaScript를 줄이고, 보안과 성능을 개선하며, 유지보수하기 쉬운 구조를 만들 수 있습니다.