[Next.js] 서버 컴포넌트와 클라이언트 컴포넌트

jiny·2025년 10월 29일

기술 면접

목록 보기
71/78

🗣️ 서버 컴포넌트와 클라이언트 컴포넌트는 각각 어느 상황에 사용되나요?

  • 의도: 지원자가 서버 컴포넌트와 클라이언트 컴포넌트를 이해하고 적절하게 사용할 수 있는지 평가
    • 서버 컴포넌트와 클라이언트 컴포넌트의 차이점을 설명한다.
    • 각각의 장단점을 설명한다.
    • 서버 컴포넌트와 클라이언트 컴포넌트를 사용하는 예를 떠올려 본다.
  • 주어진 답안 (모범 답안)

    Next.js에서 서버 컴포넌트와 클라이언트 컴포넌트는 각각 다른 용도로 사용되며, 성능과 사용자 경험에 중요한 영향을 미칩니다.

    서버 컴포넌트는 서버에서 렌더링되며, 브라우저로 전송되기 전에 HTML이 미리 생성됩니다.
    따라서 초기 로딩 속도가 빠르고, 검색 엔진 최적화에 유리합니다.
    서버에서 데이터를 가져오고 렌더링할 수 있으며, 불필요한 자바스크립트 번들을 줄여 성능 최적화가 가능합니다.
    예를 들어, getServerSideProps를 활용하여 데이터를 가져와 서버에서 미리 렌더링하는 방식으로 사용됩니다.

    클라이언트 컴포넌트는 브라우저에서 실행되며, 사용자의 상호작용이 필요한 UI에 적합합니다.
    예를 들어, 버튼 클릭, 폼 입력, 모달 팝업 등의 동적인 기능을 구현할 때 사용됩니다.
    데이터를 가져올 때 useEffect 훅을 사용하여 클라이언트 측에서 비동기 요청을 수행합니다.
    클라이언트에서 실행되므로 서버 컴포넌트보다 초기 로딩 속도가 느릴 수 있지만, 사용자 경험을 풍부하게 만들 수 있습니다.

    마지막으로 정리하자면, 서버 컴포넌트성능 최적화SEO에 강점을 가지며, 클라이언트 컴포넌트사용자와의 상호작용이 많은 경우에 유용합니다.
    따라서, 프로젝트의 요구사항에 따라 적절히 혼합하여 사용하는 것이 중요합니다.


📝 개념 정리

🌟 서버 컴포넌트란?

  • 기본 개념
    • 서버에서 렌더링되는 React 컴포넌트
    • 브라우저로 JavaScript 번들이 전송되지 않음
    • 데이터 fetching, DB 접근, 보안 관련 연산직접 수행할 수 있음
    • 렌더링 결과HTML 형태로 브라우저에 전송
  • 작동 원리
    1. 서버가 해당 컴포넌트를 렌더링할 때 HTML과 RSC Payload(JSON 형태의 구조 데이터)를 생성함
    2. 브라우저는 이 HTML을 그대로 렌더링하므로, 초기 로딩이 매우 빠름
    3. 이때 JS 실행은 필요하지 않기 때문에 클라이언트 성능을 크게 절약할 수 있음

      🪄 RSC(React Server Components)란?

      • React 컴포넌트를 서버에서 렌더링하고, 그 결과만 클라이언트로 전송하는 기술
      • React가 클라이언트(브라우저)에서만 동작하던 기존 방식과 달리, 서버에서 React 컴포넌트를 먼저 실행하고, 그 결과 HTML + 구조 데이터(JSON 형태)만 브라우저로 보내는 개념
      • 브라우저에 불필요한 JS를 보내지 않고 더 빠르고 안전한 렌더링을 가능하게 함

      🪄 RSC의 동작 구조

      RSC는 단순히 SSR(Server-Side Rendering)보다 더 정교하게 작동함
      SSR은 완성된 HTML만 보내지만, RSC는 HTML + 컴포넌트 트리 구조(RSC Payload)를 함께 전송함

      1. 서버에서 React 컴포넌트를 실행
        • fetch(), DB 접근 등 서버 전용 로직 처리 가능
      2. React가 RSC Payload(JSON 비슷한 데이터)를 생성
        • 렌더링된 UI 구조와 props 등의 메타데이터 포함
      3. 클라이언트는 이 Payload를 해석해 React 트리를 복원함(hydrate)
        • 브라우저에서는 JS 로직 없이 HTML을 즉시 표시
        • 필요한 부분만 클라이언트 컴포넌트로 하이드레이션

      ➡️ RSC는 SSR + CSR의 장점을 결합한 방식

      🪄 기존 방식과의 비교

      구분CSR(클라이언트 렌더링)SSR(서버 사이드 렌더링)RSC(서버 컴포넌트)
      실행 위치브라우저서버서버+클라이언트 혼합
      초기 로딩느림 (JS 다운로드 필요)빠름매우 빠름
      번들 크기중간가장 작음
      보안낮음 (API 키 노출 위험)높음매우 높음
      상태 관리클라이언트서버서버/클라이언트 분리
      상호작용 처리클라이언트클라이언트클라이언트만 담당

      🪄 RSC가 보안이 높다고 평가받는 이유

      1. 민감 정보가 브라우저에 가지 않음
        • DB 쿼리, API 키, 인증 토큰 등은 서버에서만 실행되고, 그 결과(HTML or 데이터 조각)만 클라이언트로 전달
        • 따라서 사용자에게 내부 로직이 절대 노출되지 않음
      2. 브라우저에 JS 번들이 거의 없음
        • 클라이언트로 전달되는 건 HTML 결과뿐이며, 로직이 담긴 JS 파일은 없음
        • 따라서 리버스 엔지니어링(코드 분석)을 통해 내부 로직을 유출하기 어려움
      3. DB 직접 접근 가능 (서버 환경)
        • RSC는 서버에서 실행되므로 DB에 직접 쿼리할 수 있음
        • 기존 CSR 환경에서는 fetch('/api/users')처럼 중간 API 서버를 둬야 했지만, RSC는 서버 내부에서 곧바로 Prisma, MongoDB, PostgreSQL 등 접근 가능
      4. 데이터 전송 최소화
        • 클라이언트로는 오직 렌더링 결과만 전송
        • JSON/HTML 형태로 직렬화되어 전달되므로, 내부 비즈니스 로직이 포함되지 않음
        • 공격자가 네트워크 트래픽을 가로채더라도 UI 구조 외의 정보는 얻기 어려움
      5. 서버-클라이언트 경계가 명확
        • "use client" 지시어로 "이 부분만 브라우저에서 실행된다"가 명확히 구분됨
        • 실수로 서버 로직을 클라이언트에 넣는 일이 거의 없음
        • 즉, 코드 레벨에서 보안 경계가 문법적으로 보장

      🪄 RSC Payload란?

      RSC가 단순히 HTML만 반환하지 않는 이유는, React 트리의 구조 정보도 함께 전달하기 때문임

      • 이 데이터가 바로 RSC Payload
      • 일종의 React 컴포넌트 트리 스냅샷 역할을 함
      • 클라이언트는 이 데이터를 해석해 하이드레이션을 최소화

      즉, RSC는 단순 SSR이 아니라 React 단위로 렌더링 정보를 직렬화(serialized)해 전송하는 구조임

      🪄 RSC와 SSR의 차이점

      구분SSRRSC
      반환 결과완성된 HTMLHTML+React Payload
      렌더 단위전체 페이지 (새 요청마다 전면 재계산)컴포넌트 단위 (부분 갱신 가능)
      데이터 전달브라우저에서 재요청 필요서버 렌더링 시 함께 포함
      Suspense/Streaming제한적완벽 지원
      React 특성 반영제한적완전 React-native 구조
  • 주요 특징

    구분설명
    실행 위치서버에서만 실행
    JavaScript 번들브라우저로 전송되지 않음
    React 훅useState, useEffect클라이언트 훅 사용 불가
    데이터 접근직접 DB 쿼리, API 호출 가능
    보안민감한 데이터 노출 위험 없음 (서버 내부에서 처리됨)
    SEOSSR 결과가 HTML로 바로 반환되어 SEO에 유리
    예시layout.tsx, page.tsx, 서버 데이터 처리용 컴포넌트

🌟 클라이언트 컴포넌트란?

  • 기본 개념
    • 브라우저에서 실행되는 컴포넌트
    • 상태(state), 이벤트 핸들러(onClick 등), React 훅(useEffect 등)을 사용할 수 있음
    • "use client" 지시어파일 최상단에 명시되어야 함
  • 주요 특징

    구분설명
    실행 위치브라우저 (클라이언트 측)
    JavaScript 번들브라우저로 전송되어 실행됨
    React 훅useState, useEffect, useRef 등 사용 가능
    브라우저 APIwindow, document, localStorage 접근 가능
    상호작용클릭, 입력, 스크롤 등 사용자 이벤트 처리 가능
    예시버튼, 모달, 폼, 탭, 드롭다운 등 UI 인터랙션 요소

🌟 두 컴포넌트의 차이 비교

항목서버 컴포넌트클라이언트 컴포넌트
실행 위치서버브라우저
선언 방식기본"use client" 필요
상태 관리 (useState 등)❌ 불가⭕ 가능
이벤트 핸들러 (onClick 등)❌ 불가⭕ 가능
브라우저 API 접근❌ 불가⭕ 가능
보안높음상대적으로 낮음
번들 크기작음커질 수 있음
데이터 fetch서버에서 직접 처리API 호출만 가능
사용 예시페이지, 데이터 표시폼, 버튼, 인터랙션 UI

🌟 서버 컴포넌트 예제

// app/posts/page.tsx (Server Component)
export default async function Page() {
  const res = await fetch("https://api.example.com/posts", { cache: "no-store" });
  const posts = await res.json();
  
  return (
    <main>
      <h1>게시글 목록</h1>
      <ul>
        {posts.map((p) => (
          <li key={p.id}>{p.title}</li>
        ))}
      </ul>
    </main>
  );
}

💻 파일 위치와 역할

  • app/posts/page.tsx 파일은 Next.js의 App Router 구조에서 /posts 경로를 담당하는 페이지
  • 즉, 사용자가 /posts URL로 접속하면 이 컴포넌트가 실행됨
  • App Router에서는 모든 page.tsx 파일이 기본적으로 서버 컴포넌트

💻 export default async function Page()

  • async function서버에서 데이터를 비동기(fetch)로 가져올 수 있음을 의미함
  • 서버 컴포넌트는 await/async를 자유롭게 사용할 수 있음
  • 클라이언트 컴포넌트는 useEffect 안에서만 fetch 가능하지만, 서버 컴포넌트는 함수 본문 안에서 바로 await fetch()가 가능함

💻 fetch("https://api.example.com/posts", { cache: "no-store" })

  • 작동 방식
    • Next.js의 fetch()서버 환경에서 실행됨 (서버 컴포넌트의 경우)
    • 브라우저 네트워크 요청이 아닌 Node.js 서버의 내부 요청
  • { cache: "no-store" } 옵션
    이 옵션은 Next.js의 데이터 캐싱 정책을 제어함

    옵션설명
    "force-cache"기본값, 결과를 캐시에 저장하고 재사용
    "no-store"캐시하지 않고 매 요청마다 새로운 데이터 요청
    "revalidate"ISR(Incremental Static Regeneration)용 설정

💻 const posts = await res.json();

  • 서버에서 받은 응답(Response 객체)을 JSON으로 변환함
  • posts 변수에는 게시글 목록 데이터 배열이 저장됨

💻 JSX 렌더링 부분

  • 서버에서 데이터를 다 받아온 후, JSX를 HTML로 서버에서 미리 렌더링(SSR)
  • 이 결과가 HTML 형태로 브라우저에 전송
  • 즉, 브라우저는 JS 실행 없이 완성된 HTML을 바로 렌더링

🌟 클라이언트 컴포넌트 예제

"use client";

import { useState } from "react";

export default function LikeButton() {
  const [liked, setLiked] = useState(false);
  
  return (
    <button
      className={`p-2 rounded ${liked ? "bg-red-500" : "bg-gray-200"}`}
      onClick={() => setLiked(!liked)}>
      {liked ? "❤️" : "♡"}
    </button>
  );
}

LikeButton은 보통 서버 컴포넌트 내부에 포함되어 사용됨

// app/posts/page.tsx (Server Component)
import LikeButton from "./LikeButton";

export default async function Page() {
  const posts = await getPosts();
  return (
    <main>
      {posts.map((p) => (
        <div key={p.id}>
          <h2>{p.title}</h2>
          <LikeButton /> {/* 여기는 브라우저에서만 실행됨 */}
        </div>
      ))}
    </main>
  );
}
  • Page는 서버에서 렌더링 (HTML 생성)
  • LikeButton만 클라이언트에서 작동 (이벤트 핸들링)

➡️ 즉, 하이브리드 렌더링이 이루어짐


🌟 실제 프로젝트에서의 활용 전략

상황선택할 컴포넌트
데이터 조회 및 렌더링서버 컴포넌트
사용자 입력 처리 (onClick, onChange 등)클라이언트 컴포넌트
정적 콘텐츠 (소개글, 문서 등)서버 컴포넌트
다크 모드 토글, 검색창, 모달클라이언트 컴포넌트
복잡한 API 연동, SEO 고려 페이지서버 컴포넌트 중심 + 필요한 부분만 클라이언트화

0개의 댓글