[Next.js] 'use client'에 대한 오해 풀어보기

김태민·2025년 9월 19일

'use client' 란?

Next.js에서는 기본적으로 모든 컴포넌트가 서버 컴포넌트로 동작합니다.

하지만, 컴포넌트가 클라이언트에서 동작해야 하는 경우도 있습니다.
ex) React Hook 사용, local storage 사용

이러한 컴포넌트에 'use client'를 선언합니다. '이 컴포넌트는 클라이언트에서 실행됩니다' 라는 뜻입니다.

제가 했던 오해

샐러드 판매 사이트를 제작하던 중, 구현은 끝났으니 이제 SEO를 위해 시도중이였습니다. 아무래도 판매 사이트니, 검색 노출이 중요할 것 같았기 때문이죠.

사용자와 상호작용이 많다보니, 페이지마다 'use client'를 사용했습니다. 그래서 제가 한 생각은, 'use client를 사용했으니 이 페이지는 클라이언트에서 렌더링되고, 그렇다면 서버에서 렌더링될 때는 빈 껍데기로 보일 것이다. 그러니 정적 컨텐츠는 서버 컴포넌트로 분리하고 상호작용이 있는 컨텐츠만 use client를 사용해야 겠다' 라는 생각을 했습니다.

그 전에, 서버에서 렌더링 될 때 어떠한 상태인지 보기 위해 view-source로 html 상태롤 확인해 보았습니다.

실제로는?


(view-souce:ezysalad.store 을 했을 때 상태입니다)

어라? 분명 'use client'를 사용했음에도 빈 껍데기가 아니라, 내용이 확인이 되고 있습니다. 'use client'를 쓴다고 페이지 전체가 클라이언트에서 렌더링 되는게 아닌 거 같다는 생각이 들었습니다.

확인

"use client";

import { useState, useEffect } from "react";

export default function TestPage() {
  const [data, setData] = useState("");
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    // 클라이언트에서만 실행되는 로직
    setData("클라이언트에서 로딩된 데이터");
    setMounted(true);
  }, []);

  return (
    <div style={{ padding: "50px" }}>
      <h1>SSR 테스트 페이지</h1>

      <div style={{ border: "1px solid red", padding: "10px", margin: "10px" }}>
        <h3>정적 콘텐츠 (서버에서 렌더링)</h3>
        <p>이 텍스트는 서버에서 미리 렌더링됩니다</p>
      </div>

      <div style={{ border: "1px solid blue", padding: "10px", margin: "10px" }}>
        <h3>useEffect로 로딩되는 콘텐츠</h3>
        <p>데이터: {data || "로딩 중..."}</p>
        <p>마운트 상태: {mounted ? "마운트됨" : "마운트 안됨"}</p>
      </div>

      <div style={{ border: "1px solid green", padding: "10px", margin: "10px" }}>
        <h3>조건부 렌더링</h3>
        {mounted ? (
          <p> 클라이언트에서만 보이는 내용</p>
        ) : (
          <p> 서버/클라이언트 초기 상태</p>
        )}
      </div>
    </div>
  );
}

서버에서의 렌더링 상태를 확인하기 위해, 임시로 페이지를 하나 작성했습니다. 결과는?

<h1>SSR 테스트 페이지</h1><div style="border:1px solid red;padding:10px;margin:10px">
<h3>정적 콘텐츠 (서버에서 렌더링)</h3>
<p>이 텍스트는 서버에서 미리 렌더링됩니다</p></div>
<div style="border:1px solid blue;padding:10px;margin:10px"><h3>useEffect로 로딩되는 콘텐츠</h3>
<p>데이터: <!-- -->로딩 중...</p><p>마운트 상태: <!-- -->마운트 안됨</p></div>
<div style="border:1px solid green;padding:10px;margin:10px"><h3>조건부 렌더링</h3>
<p>⏳ 서버/클라이언트 초기 상태</p></div>
</div><!--$?--><template id="B:1"></template><!--/$--></main><style data-emotion="css cvf69y-FooterContainer">

'use client'를 사용해도 서버에서의 페이지 내용이 보이고, 다만 클라이언트 환경에서 동작하는 useEffect를 통한 데이터만 보이지 않는 상황입니다.

이유는?

바로 Next.js의 Hydration 과정 때문입니다.

  1. 서버 단계 (SSR)

    • 'use client' 컴포넌트도 서버에서 초기 HTML 생성
    • useState의 초기값, JSX 구조 등이 HTML로 변환
    • 단, useEffect나 이벤트 핸들러는 실행되지 않음
  2. 클라이언트 단계 (Hydration)

    • 서버에서 받은 HTML에 JavaScript를 "주입"
    • React가 기존 HTML과 컴포넌트를 연결
    • 이벤트 리스너 추가, useEffect 실행 등 인터랙티브 기능 활성화

제 테스트 코드로 Hydration 이해하기

제가 작성한 테스트 코드의 각 부분이 어떻게 동작하는지 보면:

"use client";
export default function TestPage() {
  const [data, setData] = useState("");  // 1. 서버: "", 클라이언트: ""에서 시작
  const [mounted, setMounted] = useState(false);  // 2. 서버: false로 렌더링
  
  useEffect(() => {
    // 3. Hydration 후 클라이언트에서만 실행
    setData("클라이언트에서 로딩된 데이터");
    setMounted(true);
  }, []);

  return (
    <div>
      {/* 4. 서버에서 HTML로 렌더링 됨 */}
      <p>이 텍스트는 서버에서 미리 렌더링됩니다</p>
      
      {/* 5. 서버: "로딩 중...", Hydration 후: "클라이언트에서 로딩된 데이터" */}
      <p>데이터: {data || "로딩 중..."}</p>
      
      {/* 6. 서버: "서버/클라이언트 초기 상태", Hydration 후: "클라이언트에서만 보이는 내용" */}
      {mounted ? (
        <p>클라이언트에서만 보이는 내용</p>
      ) : (
        <p>서버/클라이언트 초기 상태</p>
      )}
    </div>
  );
}

결론

'use client'를 사용해도 초기 렌더링은 서버에서 일어나고, 서버에서 렌더링 가능한 부분들은 서버에서 렌더링이 된다!

profile
빠르게 성장하는 개발자

0개의 댓글