use client 지시어 활용
use client가 만드는 모듈 경계와 하위 의존성의 번들 포함 범위를 이해하고 선언 위치를 최소화합니다.
Next.js App Router에서 컴포넌트의 렌더링 환경(서버/클라이언트)을 정하는
가장 중요한 기준은 "use client" 지시어입니다.
이 문자열을 파일 맨 위에 선언하면 해당 파일과 정적 import 의존성이 클라이언트 번들 경계에 들어갑니다.
상태, effect와 이벤트 코드는 브라우저에서 실행되지만 첫 방문의 초기 HTML 생성에는 서버가 참여할 수 있습니다.
이 절에서는 'use client'의 정확한 역할과 사용법,
그리고 내부 동작 메커니즘을 실무 관점에서 살펴봅니다.
지시어가 붙은 파일 아래의 import가 어떻게 클라이언트 graph로 확장되는지 추적한다.
- includedSearchInput
SearchBox가 import하므로 client bundle에 포함
- includeddebounce helper
브라우저에서 실행되는 의존성
- prop boundaryinitialQuery
Server parent가 직렬화 가능한 값 전달
- forbiddenDB module
secret·server-only 코드는 import하면 안 됨
'use client' 지시어의 역할과 중요성
버튼 하나를 위해 페이지 전체를 client로 만들지 말고 실행 경계를 leaf 가까이에 둔다.
- serverPage · layout
데이터·markup·secret을 서버에 유지
- directiveuse client
브라우저 번들이 시작되는 파일 경계
- importsClient subtree
하위 의존성과 라이브러리가 함께 전송
- leafButton · form
실제 state와 event가 필요한 최소 영역
App Router의 기본 동작은 모든 컴포넌트를 서버 컴포넌트로 렌더링하는 것입니다.
이 방식은 초기 성능, SEO, 보안 측면에서 강점이 있습니다.
다만 상호작용, 브라우저 API 접근, 클라이언트 상태 관리처럼 React의 핵심 기능 일부는 브라우저 환경에서만 가능합니다.
'use client' 지시어는 이 경계를 명확히 하여
필요한 컴포넌트만 클라이언트 번들에 포함되게 만듭니다.
'use client'의 핵심 역할
- 컴파일러 지시어: Next.js 컴파일러와 번들러(Webpack, Turbopack)에게 해당 파일이 클라이언트 컴포넌트 그래프의 시작점임을 알립니다. 이 지시어가 없으면 파일은 서버 컴포넌트로 간주됩니다.
- JavaScript 번들 포함:
'use client'가 선언된 파일은 클라이언트 컴포넌트 그래프의 진입점이 되며, 그 파일이 임포트하는 모듈은 클라이언트 번들 경계 안으로 들어갑니다. 서버에서 렌더링한 UI를 클라이언트 컴포넌트 안에 끼워 넣어야 한다면 직접 임포트가 아니라children이나 slot 형태로 전달합니다. - 하이드레이션(Hydration) 트리거: 서버에서 렌더링된 HTML이 클라이언트에 도착하면,
'use client'로 표시된 컴포넌트들은 클라이언트 측 JavaScript로 하이드레이션되어 상호작용 가능한 상태가 됩니다. - React 훅 및 브라우저 API 사용 가능: 해당 파일 내에서
useState,useEffect,window,document등의 클라이언트 전용 기능들을 사용할 수 있게 합니다.
'use client' 사용법
'use client' 지시어는 매우 간단하게, 컴포넌트 파일의 가장 상단에 위치해야 합니다.
다른 임포트 문이나 코드보다 먼저 와야 합니다.
"use client"; // 🚨 이 지시어는 항상 파일 맨 위에 와야 합니다.
import React, { useState } from 'react';
export default function InteractiveButton() {
const [clicked, setClicked] = useState(false);
const handleClick = () => {
setClicked(true);
alert('버튼이 클릭되었습니다!');
};
return (
<button
onClick={handleClick}
style={{
padding: '10px 20px',
fontSize: '1em',
backgroundColor: clicked ? '#28a745' : '#007bff',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
transition: 'background-color 0.3s'
}}
>
{clicked ? '클릭됨!' : '클릭하세요'}
</button>
);
}- 정확한 위치:
'use client'는 파일의 첫 번째 비-주석(non-comment) 라인이어야 합니다. - 따옴표: 반드시 작은따옴표나 큰따옴표로 감싸야 합니다. (
'use client',"use client") - 한 번만 선언: 한 번 선언되면 해당 파일이 서버와 클라이언트 모듈 그래프의 경계가 됩니다. 이 파일에서 임포트하는 하위 모듈은 클라이언트 번들 대상이 되므로, 서버 컴포넌트로 남길 UI는 부모 서버 컴포넌트에서 렌더링해
children이나 props로 넘기는 구조를 사용합니다.
'use client'가 없는 경우 vs. 있는 경우
| 특징 | 'use client' 지시어 없음 (서버 컴포넌트) | 'use client' 지시어 있음 (클라이언트 컴포넌트) |
|---|---|---|
| 실행 환경 | 서버 (빌드 시 또는 요청 시) | 클라이언트 (브라우저) |
| JavaScript 번들 | 포함되지 않음 (제로 번들) | 포함됨 |
| React 훅 사용 | ❌ (useState, useEffect 등 사용 불가) | ✅ (모든 React 훅 사용 가능) |
| 이벤트 핸들러 | ❌ (onClick, onChange 등 사용 불가) | ✅ (모든 이벤트 핸들러 사용 가능) |
| 브라우저 API | ❌ (window, document 등 접근 불가) | ✅ (window, document 등 접근 가능) |
| 데이터 페칭 | async/await로 서버에서 직접 페칭 | useEffect와 fetch (또는 SWR 등)으로 클라이언트에서 페칭 |
| 민감 정보 접근 | ✅ (DB, API 키 등 안전하게 접근) | ❌ (클라이언트에 노출될 위험) |
| SEO | 서버에서 HTML 생성, 우수 | 초기 HTML은 서버에서, 동적 콘텐츠는 클라이언트에서 생성 |
아래 다이어그램은 'use client'를 추가하기 전후로 확인해야 하는 컴파일, 번들, 하이드레이션, 디버깅 지점을 정리한 것입니다.
한 줄의 지시어가 번들 크기와 직렬화 계약에 미치는 범위를 순서대로 확인한다.
- 1Directive file
client graph의 진입점을 찾음
- 2Imports
하위 모듈 중 브라우저로 이동할 코드를 추적
- 3Props
server에서 건너오는 값의 직렬화 가능성 확인
- 4Bundle
큰 라이브러리와 중복 dependency를 측정
- 5Boundary move
상호작용 leaf로 경계를 다시 좁힘
'use client' 사용 시 고려사항 및 최적화
'use client'를 남용하면 Next.js App Router의 성능 이점을 잃을 수 있습니다.
가능한 한 최소한의 컴포넌트에만 'use client'를 선언하여 클라이언트 번들 크기를 작게 유지하는 것이 중요합니다.
리프까지 내려가기 (Move Client Components to the Leaves): 상호작용이 필요한 클라이언트 컴포넌트를 가능한 한 컴포넌트 트리의 가장 깊은 곳(리프 노드) 으로 옮기세요.
예를 들어, 전체 페이지가 상호작용할 필요는 없고 헤더의 토글 버튼만 상호작용이 필요하다면, 전체 헤더를 클라이언트 컴포넌트로 만들지 말고 토글 버튼만 클라이언트 컴포넌트로 분리하세요.
// Bad (전체 헤더가 클라이언트)
"use client";
export default function Header() {
// ... 복잡한 정적 콘텐츠와 작은 토글 버튼
return <header>... <ToggleButton /> ...</header>;
}
// Good (토글 버튼만 클라이언트)
// components/Header.tsx (서버 컴포넌트)
import ToggleButton from './ToggleButton';
export default function Header() {
return <header>... <ToggleButton /> ...</header>;
}
// components/ToggleButton.tsx
"use client";
export default function ToggleButton() {
const [open, setOpen] = useState(false);
return <button onClick={() => setOpen(!open)}>Toggle</button>;
}이렇게 하면 헤더의 대부분의 정적 HTML은 서버에서 렌더링되고, 오직 토글 버튼의 작은 JavaScript 코드만 클라이언트에 전송됩니다.
children prop 활용:
클라이언트 컴포넌트가 서버 컴포넌트의 children prop을 받는 경우, children은 이미 서버에서 HTML로 렌더링된 결과물입니다.
이 HTML은 클라이언트 컴포넌트의 JavaScript 번들에 포함되지 않습니다.
이 패턴을 활용하면 클라이언트 컴포넌트가 서버 컴포넌트를 직접 임포트할 수 없는 제약을 우회하고, 서버에서 생성된 콘텐츠를 클라이언트에서 동적으로 조작할 수 있습니다.
import ClientWrapper from './ClientWrapper';
import ServerOnlyContent from './ServerOnlyContent';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<ClientWrapper>
<ServerOnlyContent /> {/* 서버에서 렌더링되어 children으로 전달됨 */}
{children} {/* 페이지 콘텐츠도 children으로 전달될 수 있음 */}
</ClientWrapper>
);
}"use client";
// 이 파일은 클라이언트 컴포넌트입니다.
export default function ClientWrapper({ children }: { children: React.ReactNode }) {
// children은 이미 서버에서 생성된 HTML 또는 클라이언트 컴포넌트
return (
<div>
{/* 여기서 children을 조건부 렌더링하거나 다른 상호작용 추가 */}
{children}
</div>
);
}서버 전용 코드 분리: 클라이언트 컴포넌트 내에서 서버 전용 코드를 실수로 포함하지 않도록 주의하세요.
예를 들어, 클라이언트 컴포넌트가 민감한 API 키를 직접 포함하거나, 데이터베이스 연결 로직을 포함해서는 안 됩니다.
데이터 페칭이 필요하다면, 클라이언트에서 사용할 수 있는 /api 라우트 핸들러를 호출하거나, 서버 컴포넌트에서 데이터를 받아 props로 전달하는 방식을 사용해야 합니다.
'use client'는 편리하지만 선언 위치가 넓어질수록 클라이언트 번들도 함께 커집니다.
아래 다이어그램은 Header/ToggleButton 예시를 더 일반화한 페이지 import 그래프로 보고, 지시어를 붙이기 전후로 확인할 경계, props, 번들 범위를 정리합니다.
지시어를 위로 올릴수록 브라우저 번들과 hydration 범위가 함께 넓어진다.
- serverPage · Layout
데이터·metadata·공통 HTML을 서버에 유지
- boundarySerializable props
클라이언트에 필요한 최소 값만 전달
- clientInteractive leaf
event·state·browser API를 가까운 파일에 배치
- costImported subtree
경계 아래 모듈이 브라우저 실행 후보가 됨
실제 컴포넌트를 나눌 때는 기능 요구, 데이터 출처, 번들 영향, 배포 환경을 함께 보고 경계를 확정해야 합니다.
브라우저 실행이 필요한 이유와 서버에 남겨야 할 책임을 동시에 확인한 뒤 경계 위치를 확정한다.
- hook·event 없음서버 유지
초기 HTML과 정적 표현만 필요한 영역
- state·event 필요작은 client leaf
사용자 동작에 바로 반응하는 최소 파일
- DB·secret 포함서버로 분리
민감 로직을 client import 트리 밖으로 이동
- bundle 과대경계 재분할
무거운 의존성과 상호작용 코드를 서로 분리
'use client' 지시어는 Next.js App Router에서 서버와 클라이언트 컴포넌트의 명확한 분리를 가능하게 하는 핵심 도구입니다.
이 지시어의 작동 원리와 최적화 전략을 이해하고 적용함으로써, 성능, 보안, 그리고 사용자 경험 모두를 향상시키는 효율적인 Next.js 애플리케이션을 구축할 수 있습니다.
use client 지시어 활용의 판단 흐름을 화면 결과, 서버 비용, 운영 신호 기준으로 다시 묶었습니다.
초기 HTML은 서버에서 미리 만들어질 수 있지만, 경계 아래 JavaScript는 브라우저 번들과 hydration 대상이 된다.
- Server Component
데이터·비밀·정적 UI를 서버에 남긴다.
- 'use client'
파일과 import 하위 트리가 경계를 넘는다.
- 직렬화 가능한 props
서버 결과만 작은 상호작용 섬에 전달한다.
- Client island
JS가 이벤트를 붙이고 상태를 이어받는다.
- 클라이언트가 필요한 신호
state·event handler effect·custom hook window · localStorage 브라우저 전용 라이브러리
- 서버에 남길 책임
DB·API 가까운 fetch 키·토큰·권한 처리 상호작용 없는 본문 큰 의존성과 변환 작업
마지막으로 use client 지시어가 컴포넌트 실행 위치와 번들 범위에 미치는 영향을 정리합니다.
state·event·browser API가 있는지 확인하고 서버 대안이 없을 때만 선언한다.
- 표시만서버 유지
데이터 조회와 정적 UI는 브라우저 실행 불필요
- 로컬 상태Client leaf
버튼·폼·탭 파일에만 지시어 선언
- window APIClient boundary
브라우저 전용 기능을 작은 영역에 격리
- 상위 선언경계 재분할
하위 import 전체가 번들되는지 확인