[Next.js] Server Component & Client Component

chaen·2025년 2월 15일

REACT / NEXT.js

목록 보기
13/22
post-thumbnail

🧐 서버 컴포넌트와 클라이언트 컴포넌트란?

Next.js의 App Router에서는 컴포넌트가 서버 컴포넌트(Server Component, RSC) 또는 클라이언트 컴포넌트(Client Component)로 나뉜다.

서버 컴포넌트 (Server Component)

  • JS 번들링(클라이언트 전달용)이 필요 없음 → 서버에서만 실행됨.
  • 초기 HTML을 생성하여 클라이언트로 전달 (빠른 렌더링 & SEO 최적화 가능).
  • 상호작용(이벤트 핸들러 등)이 필요 없는 경우, 굳이 클라이언트에서 실행할 필요 없음.
  • console.log("server")를 찍으면 터미널에서만 출력됨.

대표적인 사용 예시
✔ 데이터베이스 요청 (DB 조회, API 호출)
✔ SEO가 중요한 정적인 페이지
✔ 초기 로딩이 빠른 UI를 제공할 때
✔ 인터렉션이 없는 모든 페이지

// 서버 컴포넌트 (기본값, "use client"가 없음)

export default async function ServerComponent() {
  console.log("서버에서만 실행됨!");
  const data = await fetch("https://api.example.com/data").then((res) => res.json());

  return <div>서버에서 가져온 데이터: {data.name}</div>;
}

클라이언트 컴포넌트 (Client Component)

  • 상호작용이 필요한 경우 필수 (onClick, useState, useEffect 등 사용 가능).
  • 서버 + 클라이언트 양쪽에서 실행됨 (렌더링이 첫 js 실행, js 번들링으로 2번 일어나므로 브라우저에서만 실행되도록 하려면 useEffect 필요).
  • 클라이언트에서만 실행되도록 하려면 상단에 "use client" 선언이 필요함.

대표적인 사용 예시
✔ 버튼 클릭, 입력 필드, 모달, 드롭다운 등
✔ 브라우저에서 실행해야 하는 기능 (localStorage, window 객체 등)

// 클라이언트 컴포넌트 (상단에 "use client" 선언 필수)
"use client";

import { useState } from "react";

export default function ClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>클라이언트에서 실행되는 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

🔥 서버 & 클라이언트 컴포넌트의 렌더링 과정

  1. 사용자가 브라우저에서 서버에 접속 요청
  2. 서버에서 JS 실행 → 페이지 렌더링 진행
  • 서버 컴포넌트 먼저 렌더링 → RSC (React Server Component) Payload 생성
  • 클라이언트 컴포넌트는 이후 실행됨
  1. 렌더링된 HTML이 클라이언트로 전송됨
  2. 브라우저에서 HTML을 화면에 표시 (FCP - First Contentful Paint)
  3. 서버에서 클라이언트로 JS Bundle 전송
  4. Hydration (수화) 진행 → 클라이언트 컴포넌트가 실행됨
  5. 사용자가 UI와 상호작용 가능 (TTI - Time to Interactive)

💡 정리하면?

  • 서버 컴포넌트는 HTML에 포함되어 우선 렌더링 됨 → 빠른 FCP (SEO, 초기 로딩 성능 우수)
  • 클라이언트 컴포넌트만 Hydration 할 때 재실행 → 인터랙션 가능해짐

⚠️ 주의사항

1. 클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import 할 수 없다 ❌

📌 서버 컴포넌트는 서버에서만 실행되는데, 클라이언트 컴포넌트는 두 번 실행되므로 불가능!

  • 만약 클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import하면, 클라이언트에서 강제로 서버 컴포넌트를 실행해야 하므로 오류 발생 ⚠️
  • Next.js는 오류 방지를 위해 서버 컴포넌트를 강제로 클라이언트 컴포넌트로 변환시킴. 이 과정에서 불필요한 클라이언트 컴포넌트가 발생하므로 지양할 것.

❌ 잘못된 예시

"use client";
import ServerComponent from "./ServerComponent"; // ❌ 오류 발생

export default function ClientComponent() {
  return <ServerComponent />;
}

올바른 해결 방법
서버 컴포넌트를 직접 import하지 않고, children으로 넘겨서 렌더링해야 한다.

"use client";

export default function ClientComponent({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}
// ServerComponent.tsx
export default function ServerComponent() {
  return <p>이것은 서버 컴포넌트입니다!</p>;
}
// 사용 예시
import ServerComponent from "./ServerComponent";
import ClientComponent from "./ClientComponent";

export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  );
}

2. 서버 컴포넌트에서 클라이언트 컴포넌트로 직렬화되지 않는 props는 전달 불가

📌 직렬화(Serialization)란?
서버에서 데이터를 전송할 때, { name: "Alice", age: 18 } 같은 객체는 직렬화가 가능하지만,
함수(() => {}), Date, Map, Set, RegExp 같은 데이터는 직렬화가 불가능하다.

❌ 잘못된 예시 (서버 컴포넌트 → 클라이언트 컴포넌트로 함수 전달)

export default function ServerComponent() {
  const handleClick = () => {
    console.log("클릭!");
  };

  return <ClientComponent onClick={handleClick} />; // ❌ 오류 발생
}

⚠️ 왜?

  • 서버 컴포넌트는 처음 렌더링 시 서버에서 HTML을 생성하고, RSC Payload를 생성한다.
  • 이 과정에서 handleClick 함수는 문자열로 변환할 수 없으므로, 클라이언트 컴포넌트로 전달되지 않음.
  • 따라서, 클라이언트에서 실행해야 하는 로직은 클라이언트 컴포넌트 내부에서 정의해야 함.

✅ 올바른 해결 방법 (클라이언트에서 함수 정의)

"use client";

export default function ClientComponent() {
  const handleClick = () => {
    console.log("클릭!");
  };

  return <button onClick={handleClick}>클릭</button>;
}

🎯 정리

구분서버 컴포넌트 (Server Component)클라이언트 컴포넌트 (Client Component)
실행 위치서버에서만 실행서버 & 클라이언트 모두 실행
useState, useEffect 사용❌ 불가능✅ 가능
이벤트 핸들러 (onClick)❌ 불가능✅ 가능
SEO 최적화✅ 가능❌ 어려움
JS 번들 크기📉 줄어듦📈 커질 수 있음
직접 import 가능?✅ 가능❌ 불가능 (children으로 전달해야 함)
직렬화 불가능한 데이터 전달❌ 불가능✅ 가능 (내부에서 정의)

🚀 결론

  • 최대한 서버 컴포넌트를 활용하고, 꼭 필요한 부분만 클라이언트 컴포넌트로 만들 것!
  • 클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import하지 않도록 주의!
  • 서버에서 이벤트 핸들러 등 직렬화되지 않는 데이터를 직접 전달하지 말 것!

0개의 댓글