icon안동민 개발노트

use client 지시어 활용


 'use client' 지시어는 Next.js에서 서버 컴포넌트와 클라이언트 컴포넌트의 경계를 명확히 구분하는 중요한 도구입니다. 이 절에서는 'use client' 지시어의 정확한 사용법, 영향 범위, 그리고 성능에 미치는 영향에 대해 자세히 알아보겠습니다.

'use client' 지시어의 정확한 사용법

 'use client' 지시어는 파일의 최상단에 작성하며, 모든 임포트 구문보다 앞에 위치해야 합니다.

'use client'
 
import { useState } from 'react'
 
export default function Counter() {
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}

영향 범위

 'use client' 지시어는 해당 파일과 그 파일에서 임포트하는 모든 모듈에 영향을 미칩니다. 즉, 한 번 클라이언트 컴포넌트로 선언된 파일에서 임포트하는 모든 컴포넌트도 클라이언트 컴포넌트로 취급됩니다.

// ClientComponent.js
'use client'
 
import ChildComponent from './ChildComponent'
 
export default function ClientComponent() {
  return <ChildComponent />
}
 
// ChildComponent.js
// 'use client' 지시어가 없어도 클라이언트 컴포넌트로 취급됨
export default function ChildComponent() {
  return <div>Child</div>
}

'use client' 지시어를 사용해야 하는 상황

  1. 상태를 관리해야 할 때 (useState, useReducer)
  2. 생명주기 메서드를 사용해야 할 때 (useEffect, useLayoutEffect)
  3. 브라우저 전용 API를 사용해야 할 때 (window, document 등)
  4. 이벤트 리스너를 연결해야 할 때
  5. 클라이언트 사이드 라우팅을 사용해야 할 때 (useRouter)

사용 시 주의사항

  1. 불필요한 사용 자제 : 필요한 경우에만 'use client'를 사용하여 서버 컴포넌트의 이점을 최대한 활용합니다.
  2. 컴포넌트 분리 : 클라이언트 로직과 서버 로직을 별도의 컴포넌트로 분리하여 관리합니다.
  3. 데이터 페칭 : 가능한 서버 컴포넌트에서 데이터를 페칭하고, props를 통해 클라이언트 컴포넌트로 전달합니다.

최적화 전략

  1. 지연 로딩 활용
import dynamic from 'next/dynamic'
 
const ClientComponent = dynamic(() => import('./ClientComponent'), {
  loading: () => <p>Loading...</p>,
})
 
export default function Page() {
  return (
    <div>
      <h1>Server Component</h1>
      <ClientComponent />
    </div>
  )
}
  1.  클라이언트 번들 크기 최소화 : 클라이언트 컴포넌트를 작게 유지하고, 필요한 기능만 포함시킵니다.

  2.  서버 컴포넌트 활용 극대화 : 데이터 페칭, 대규모 의존성이 필요한 로직 등은 가능한 서버 컴포넌트에서 처리합니다.

12장 성능 최적화와의 연결

 7장의 'use client' 지시어 활용은 12장의 성능 최적화와 밀접하게 연관됩니다. 'use client' 지시어를 전략적으로 사용함으로써 초기 페이지 로드 시간을 줄이고, 클라이언트 측 JavaScript 번들 크기를 최적화할 수 있습니다. 12장에서는 이러한 최적화 전략을 더 깊이 있게 다루며, 코드 분할, 지연 로딩 등의 기술과 함께 'use client' 사용을 최적화하는 방법을 학습하게 됩니다.

실습 : 클라이언트-서버 경계 최적화

 다음 요구사항을 만족하는 애플리케이션 구조를 구현해보세요.

  1. 서버에서 데이터를 페칭하는 상품 목록 페이지
  2. 클라이언트에서 상품 필터링 및 정렬 기능
  3. 클라이언트 사이드 장바구니 기능

 구현 예시

// app/products/page.js (Server Component)
import ProductList from './ProductList'
import { getProducts } from './api'
 
export default async function ProductsPage() {
  const products = await getProducts()
  return <ProductList initialProducts={products} />
}
 
// app/products/ProductList.js (Client Component)
'use client'
 
import { useState } from 'react'
import ProductItem from './ProductItem'
import ShoppingCart from './ShoppingCart'
 
export default function ProductList({ initialProducts }) {
  const [products, setProducts] = useState(initialProducts)
  const [cart, setCart] = useState([])
 
  const addToCart = (product) => {
    setCart([...cart, product])
  }
 
  const filterProducts = (category) => {
    setProducts(initialProducts.filter(p => p.category === category))
  }
 
  return (
    <div>
      <ShoppingCart items={cart} />
      <div>
        <button onClick={() => filterProducts('electronics')}>Electronics</button>
        <button onClick={() => filterProducts('clothing')}>Clothing</button>
      </div>
      {products.map(product => (
        <ProductItem key={product.id} product={product} onAddToCart={addToCart} />
      ))}
    </div>
  )
}
 
// app/products/ProductItem.js (Client Component)
export default function ProductItem({ product, onAddToCart }) {
  return (
    <div>
      <h2>{product.name}</h2>
      <p>{product.price}</p>
      <button onClick={() => onAddToCart(product)}>Add to Cart</button>
    </div>
  )
}
 
// app/products/ShoppingCart.js (Client Component)
export default function ShoppingCart({ items }) {
  return (
    <div>
      <h2>Cart ({items.length} items)</h2>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  )
}

 이 실습을 통해 서버 컴포넌트에서 데이터를 페칭하고, 클라이언트 컴포넌트에서 상호작용을 처리하는 최적화된 구조를 경험할 수 있습니다. 'use client' 지시어를 전략적으로 배치함으로써, 서버 렌더링의 이점을 최대한 활용하면서도 필요한 부분에서 클라이언트 사이드 기능을 구현할 수 있습니다.