use client 지시어 활용
Next.js App Router에서 컴포넌트의 렌더링 환경(서버 또는 클라이언트)을 결정하는 가장 중요한 요소는 바로 "use client"
지시어입니다. 이 짧은 문자열은 파일의 맨 위에 선언되어, 해당 파일이 클라이언트 컴포넌트이며 브라우저에서 실행되어야 함을 Next.js와 번들러에게 명시적으로 알려줍니다. 이 절에서는 'use client'
지시어의 정확한 역할, 사용법, 그리고 그 뒤에 숨겨진 메커니즘을 상세히 알아보겠습니다.
'use client'
지시어의 역할과 중요성
Next.js App Router의 기본 동작은 모든 컴포넌트가 서버 컴포넌트로 렌더링되는 것입니다. 이는 초기 로딩 성능, SEO, 보안 등 여러 이점을 제공합니다. 하지만 상호작용성, 브라우저 API 접근, 클라이언트 측 상태 관리 등 전통적인 React 애플리케이션의 핵심 기능들은 클라이언트 환경에서만 가능합니다. 'use client'
지시어는 이 두 환경 사이의 명확한 경계를 설정하여, 필요한 컴포넌트만 클라이언트 번들에 포함되도록 합니다.
'use client'
의 핵심 역할
- 컴파일러 지시어: Next.js 컴파일러와 번들러(Webpack, Turbopack)에게 해당 파일이 클라이언트 컴포넌트 그래프의 시작점임을 알립니다. 이 지시어가 없으면 파일은 서버 컴포넌트로 간주됩니다.
- JavaScript 번들 포함:
'use client'
가 선언된 파일과 그 하위에 임포트되는 모든 컴포넌트(명시적으로 서버 컴포넌트로 분류되지 않은 경우)는 클라이언트 측 JavaScript 번들에 포함되어 사용자 브라우저로 전송됩니다. - 하이드레이션(Hydration) 트리거: 서버에서 렌더링된 HTML이 클라이언트에 도착하면,
'use client'
로 표시된 컴포넌트들은 클라이언트 측 JavaScript로 "하이드레이션"되어 상호작용 가능한 상태가 됩니다. - React 훅 및 브라우저 API 사용 가능: 해당 파일 내에서
useState
,useEffect
,window
,document
등의 클라이언트 전용 기능들을 사용할 수 있게 합니다.
'use client'
사용법
'use client'
지시어는 매우 간단하게, 컴포넌트 파일의 가장 상단에 위치해야 합니다. 다른 임포트 문이나 코드보다 먼저 와야 합니다.
// src/app/my-component/InteractiveButton.tsx
"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"
) - 한 번만 선언: 한 번 선언되면 해당 파일뿐만 아니라, 이 파일에서 임포트하는 모든 자식 컴포넌트(명시적으로 서버 컴포넌트로 정의되지 않는 한)에도 클라이언트 컴포넌트 속성이 전파됩니다. 즉,
'use client'
는 컴포넌트 트리에서 클라이언트 경계를 정의하는 역할을 합니다.
'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'
사용 시 고려사항 및 최적화
'use client'
를 남용하면 Next.js App Router의 성능 이점을 잃을 수 있습니다. 가능한 한 최소한의 컴포넌트에만 'use client'
를 선언하여 클라이언트 번들 크기를 작게 유지하는 것이 중요합니다.
최적화 전략
-
"리프까지 내려가기" (Move Client Components to the Leaves): 상호작용이 필요한 클라이언트 컴포넌트를 가능한 한 컴포넌트 트리의 **가장 깊은 곳(리프 노드)**으로 옮기세요. 예를 들어, 전체 페이지가 상호작용할 필요는 없고 헤더의 토글 버튼만 상호작용이 필요하다면, 전체 헤더를 클라이언트 컴포넌트로 만들지 말고 토글 버튼만 클라이언트 컴포넌트로 분리하세요.
components/Header.tsx // Bad (전체 헤더가 클라이언트) // components/Header.tsx "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 번들에 포함되지 않습니다. 이 패턴을 활용하여 클라이언트 컴포넌트가 서버 컴포넌트를 직접 임포트할 수 없는 제약을 우회하고, 서버에서 생성된 콘텐츠를 클라이언트에서 동적으로 조작할 수 있습니다.layout.tsx // layout.tsx (서버 컴포넌트) import ClientWrapper from './ClientWrapper'; import ServerOnlyContent from './ServerOnlyContent'; export default function Layout({ children }: { children: React.ReactNode }) { return ( <ClientWrapper> <ServerOnlyContent /> {/* 서버에서 렌더링되어 children으로 전달됨 */} {children} {/* 페이지 콘텐츠도 children으로 전달될 수 있음 */} </ClientWrapper> ); }
ClientWrapper.tsx // ClientWrapper.tsx "use client"; // 이 파일은 클라이언트 컴포넌트입니다. export default function ClientWrapper({ children }: { children: React.ReactNode }) { // children은 이미 서버에서 생성된 HTML 또는 클라이언트 컴포넌트 return ( <div> {/* 여기서 children을 조건부 렌더링하거나 다른 상호작용 추가 */} {children} </div> ); }
-
서버 전용 코드 분리: 클라이언트 컴포넌트 내에서 서버 전용 코드를 실수로 포함하지 않도록 주의하세요. 예를 들어, 클라이언트 컴포넌트가 민감한 API 키를 직접 포함하거나, 데이터베이스 연결 로직을 포함해서는 안 됩니다. 데이터 페칭이 필요하다면, 클라이언트에서 사용할 수 있는
/api
라우트 핸들러를 호출하거나, 서버 컴포넌트에서 데이터를 받아props
로 전달하는 방식을 사용해야 합니다.
'use client'
지시어는 Next.js App Router에서 서버와 클라이언트 컴포넌트의 명확한 분리를 가능하게 하는 핵심 도구입니다. 이 지시어의 작동 원리와 최적화 전략을 이해하고 적용함으로써, 성능, 보안, 그리고 사용자 경험 모두를 향상시키는 효율적인 Next.js 애플리케이션을 구축할 수 있습니다.