Next.js에서 use client가 선언된 컴포넌트를 서버 컴포넌트에서 import하면 어떻게 될까?

김현준·2025년 5월 11일
1

넥스트JS 이모저모

목록 보기
7/23
  • 서버 컴포넌트가 클라이언트 컴포넌트를 직접 import하면, 그 컴포넌트는 클라이언트로 번들되며 서버 전용 기능을 사용할 수 없다.

정확히는 클라이언트 컴포넌트를 import하면 무조건 클라이언트로 번들되는게 아니라
클라이언트 컴포넌트를 import해서 조작하거나 내부 상태에 접근하려고 하면, 클라이언트로 번들되거나 에러가 난다.


오해하기 쉬운 개념

"use client가 선언된 컴포넌트를 서버 컴포넌트에서 import해도, 서버 컴포넌트니까 SSR 되겠지?"

하지만 실제로는:

  • 클라이언트 컴포넌트를 import한 시점에서 그 상위 컴포넌트도 클라이언트 컴포넌트로 간주되어 클라이언트 번들에 포함된다.


use client 전파 규칙

상황동작
서버 컴포넌트가 클라이언트 컴포넌트 import상위 서버 컴포넌트도 클라이언트로 번들됨
클라이언트 컴포넌트가 서버 컴포넌트 import에러 발생: 클라이언트에서는 서버 전용 기능 사용 불가
서버 컴포넌트만 사용순수 SSR 처리
클라이언트 컴포넌트만 사용CSR 처리 (Hydration 포함)

예시 코드로 이해하기

LoginButton.tsx

'use client';

export default function LoginButton() {
  return <button>Google Login</button>;
}

LoginButtons.tsx

// use client 없음 (서버 컴포넌트)
import LoginButton from './LoginButton';

export default function LoginButtons() {
  return (
    <div>
      <LoginButton />
    </div>
  );
}

이럴 때 Next.js는?

  • LoginButtonuse client가 있으므로,
    그것을 import한 LoginButtons자동으로 클라이언트 번들에 포함
    즉, 결국 둘 다 클라이언트에서 렌더링됨
  • LoginButtons 내부에 fetch, cookies, headers 같은 서버 API 사용 시 에러 발생


그렇다면 SSR은 언제 되는가?

  • use client가 없는 컴포넌트만으로 구성된 컴포넌트 트리일 때
    그리고 그 트리가 브라우저에서 사용자가 도달할 수 있도록 설정된 페이지(/app/page.tsx 등)인 경우
    이때만 진짜 SSR이 실행됨 (HTML을 서버에서 렌더링해서 보내줌)


요약

목적어떻게 해야 할까?
서버에서 HTML을 만들어 보내고 싶다면use client없는 컴포넌트 트리만 사용
클라이언트 상호작용 (버튼 클릭, 상태 관리 등)use client 선언 필수
서버 컴포넌트에서 클라이언트 컴포넌트 import가능하지만 해당 컴포넌트도 클라이언트에서 실행됨
클라이언트 컴포넌트에서 서버 컴포넌트 import불가능 (Next.js가 에러 발생시킴)

추가: use client 전파 흐름

1. 순수 서버 컴포넌트 트리 (SSR)

// app/page.tsx
export default function Page() {
  return <ServerOnlyComponent />;
}

// components/ServerOnlyComponent.tsx
export default function ServerOnlyComponent() {
  return <div>서버에서 렌더링된 내용</div>;
}

렌더링 흐름

  A[서버 컴포넌트 (page.tsx)] --> B[ServerOnlyComponent]
  B --> C[서버에서 HTML 생성]
  C --> D[브라우저로 HTML 전송]
  • 서버에서 HTML을 만들어 클라이언트에 보냄 (완전 SSR)
  • JS 번들 최소화
  • SEO에 최적

2. 서버 컴포넌트가 클라이언트 컴포넌트를 import한 경우

// app/page.tsx
import ClientComponent from '@/components/ClientComponent';

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

// components/ClientComponent.tsx
'use client';
export default function ClientComponent() {
  return <button>클릭</button>;
}

렌더링 흐름

  A[서버 컴포넌트 (page.tsx)] --> B[ClientComponent (use client)]
  B --> C[서버에서는 아무 렌더링 안 함]
  C --> D[브라우저에서 JS로 렌더링됨 (Hydration)]
  • 서버에서 HTML을 렌더링하지 않음
  • 브라우저에서만 렌더링됨 (CSR)
  • SEO 약함
  • 인터랙션 가능

3. 중간 컴포넌트가 클라이언트 컴포넌트를 import한 경우

// app/page.tsx
import Wrapper from '@/components/Wrapper';

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

// components/Wrapper.tsx
import ClientComponent from './ClientComponent';

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

// components/ClientComponent.tsx
'use client';
export default function ClientComponent() {
  return <button>클릭</button>;
}

렌더링 흐름

  A[서버 컴포넌트 (page.tsx)] --> B[Wrapper.tsx]
  B --> C[ClientComponent (use client)]
  C --> D[Wrapper도 클라이언트 컴포넌트로 번들됨]
  D --> E[브라우저에서 렌더링됨]
  • Wrapper는 use client가 없어도 클라이언트 컴포넌트로 간주됨
  • 클라이언트 번들에 포함됨
  • 서버 API(fetch 등) 사용 불가
profile
기록하자

1개의 댓글

comment-user-thumbnail
2025년 5월 26일

좋은 글 감사합니다~

답글 달기