본문 바로가기

Develog/Study

서버컴포넌트와 클라이언트 컴포넌트 잘 사용하기 (with Next.js)

 
 
 

 

1. Next.js의 서버 컴포넌트와 클라이언트 컴포넌트

서버 컴포넌트

서버 컴포넌트란 React에서 선택적으로 서버에 캐시하여 렌더링할 수 있는 컴포넌트다. Next.js에서는 라우트 세그먼트별로 세분화하여 부분 렌더링과 스트리밍이 가능하도록 설계되어 있다.

 

Next.js에서 서버 컴포넌트를 렌더링하는 전략은 다음 세 가지가 있다.

  1. 정적 렌더링: 라우트가 빌드 타임에 미리 렌더링되거나 백그라운드에서 재검증된다.
  2. 동적 렌더링: 라우트가 사용자의 각 요청 타임에 맞춰 렌더링된다.
  3. 스트리밍: 서버에서 생성된 UI가 준비되는 대로 점진적으로 클라이언트로 전송된다. 이를 통해 사용자는 전체 콘텐츠가 로딩되기 전에 일부 콘텐츠를 미리 볼 수 있다. (Next.js의 앱 라우터 기본값)

서버컴포넌트의 장점 : 

  1. 클라이언트 번들 크기 감소 : 서버에서 렌더링되므로, 브라우저로 전송되는 자바스크립트 양이 줄어들어 초기 로딩 속도 향상 및 성능 개선에 유리하다.
  2.  데이터 패칭 효율성 : 서버에서 직접 DB나 API 호출 가능해서 클라이언트에서 네트워크 요청을 줄여 속도와 사용자 경험 향상된다.
  3. 보안성 증가 : 서버에서만 실행되는 로직에 있는 민감한 데이터나 API 키는 클라이언트에 노출되지 않고 안전하게 포함된다.
  4. 렌더링 성능 최적화 : 클라이언트와 서버 작업을 분리하여 리소스 사용을 효율적으로 분산, 사용자에게 더 빠른 응답 제공이 가능하다
  5. 코드 스플리팅 자동화 : 필요한 부분만 클라이언트에 전달되므로, 더 빠르고 가벼운 서비스가 가능해진다.

클라이언트 컴포넌트

클라이언트 컴포넌트는 서버에서 미리 렌더링된 인터랙티브 UI다.

브라우저가 JavaScript를 통해 추가 작업을 수행할 수 있게 한다.

 

클라이언트 컴포넌트의 장점:

  1. Interactivity: useState, useEffect, 이벤트 리스너 등을 사용하여 사용자와 상호작용하고, UI를 동적으로 업데이트할 수 있다.
  2. Browser APIs: window, localStorage 등 브라우저 API에 접근 가능하다.

클라이언트 컴포넌트 사용법: 최상단에 'use client'를 선언하여 서버 컴포넌트와 클라이언트 컴포넌트 간의 경계를 설정한다. 'use client'가 선언된 컴포넌트 및 그 하위 자식은 모두 클라이언트 컴포넌트로 간주된다.

Next.js는 효과적인 사용을 위해 다음과 같은 패턴을 추천한다.

  1. 클라이언트 컴포넌트를 가능한 하단에 배치 클라이언트 JavaScript 번들 크기를 최소화하기 위해 트리의 가능한 하단에 클라이언트 컴포넌트를 배치한다.
// SearchBar는 클라이언트 컴포넌트
import SearchBar from './searchbar'
// Logo는 서버 컴포넌트
import Logo from './logo'

// 기본적으로 Layout은 서버 컴포넌트
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <main>{children}</main>
    </>
  )
}

예를 들어, 로고 같은 정적 컴포넌트와 검색창 같은 동적 컴포넌트를 분리하여 서버와 클라이언트 각각에 최적화하여 사용한다. 

 

2. 언제 클라이언트 컴포넌트를 쓰고 언제 서버컴포넌트를 써야 할까?

이를 다루기 위해 Next.js는 몇가지 패턴을 추천한다.

작업 내용 서버 컴포넌트 클라이언트 컴포넌트
데이터 가져오기(fetch)
백엔드 리소스에 직접 접근
민감한 정보(액세스 토큰, API 키 등)를 서버에 유지
큰 의존성을 서버에 유지하거나 클라이언트 자바스크립트를 줄이기
상호작용 및 이벤트 리스너 추가 (onClick(), onChange() 등)
상태 및 라이프사이클 이펙트 사용 (useState(), useReducer(), useEffect() 등)
브라우저 전용 API 사용
상태, 이펙트 또는 브라우저 전용 API에 의존하는 커스텀 훅 사용
React 클래스 컴포넌트 사용

 

3. 서버 컴포넌트와 클라이언트 컴포넌트 함께 사용하기 (Interleaving)

  • 서버 컴포넌트는 클라이언트 컴포넌트의 자식이 될 수 없다.
  • props로 서버 컴포넌트를 전달하는 것은 가능하다.

불가능한 경우

'use client'

// 클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import할 수 없다.
import ServerComponent from './Server-Component'

export default function ClientComponent({ children }: { children: React.ReactNode }) {
  const [count, setCount] = useState(0)

  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <ServerComponent /> {/* 불가능 */}
    </>
  )
}

 

가능한 경우

'use client'

export default function ClientComponent({ children }: { children: React.ReactNode }) {
  const [count, setCount] = useState(0)

  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {children} {/* 가능 */}
    </>
  )
}

 

클라이언트 컴포넌트는 children이 서버컴포넌트가 될 줄 모르기 때문에 받을 수 있다. 이런 구조에서 결국 ClientComponent는 오직 자식이 어디에 위치하게 될지만 결정하면 되기 때문이다.

 

그러므로, 아래와 같은 패턴이 가능해진다.

 

import ClientComponent from './client-component'
import ServerComponent from './server-component'

// Next.js의 페이지는 기본적으로 서버 컴포넌트
export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  )
}

4. 서버와 클라이언트 컴포넌트를 왜 분리할까

1. 코드 스플리팅을 통한 성능 개선
- 서버 컴포넌트는 빌드 단계에서 클라이언트 코드와 분리되므로 클라이언트에 불필요한 JavaScript가 전달되지 않는다.
- 이를 통해 클라이언트가 로딩해야 할 JavaScript 크기가 감소하여 페이지의 초기 로딩 속도가 향상된다

 

2. 클라이언트 측 JavaScript 용량 축소

- 클라이언트에서 실행해야 할 JavaScript의 양이 줄어들어, 사용자의 브라우저 성능이 개선되고 페이지 응답성이 좋아진다.

 

3. 데이터 로딩 지연에 대한 사용자 경험 개선

- 서버 및 클라이언트 각각에서 Next.js의 Suspense를 활용하여, 데이터가 준비되지 않은 상태에서의 로딩 처리를 자연스럽게 구현할 수 있다.

- 이를 통해 데이터 로딩 중에도 사용자에게 자연스럽고 직관적인 로딩 경험을 제공한다.

 

4. 초기 로딩 속도의 최적화

- 서버 컴포넌트가 먼저 렌더링 가능한 부분을 빠르게 처리하여 사용자가 더 빠르게 페이지 콘텐츠를 볼 수 있도록 한다

- 이후 상호작용이 필요한 클라이언트 컴포넌트는 비동기 지연 로딩 방식으로 처리하여, 전체적인 로딩 경험을 최적화한다.

 


5. 마치며

Next.js 프로젝트를 리팩터링 하기 전에 서버와 클라이언트 컴포넌트를 더 명확히 구분해 보았다.
공식 문서를 읽으며 제대로 정리하니 이미 사용하고 있던 패턴도 있고, 새로운 패턴도 있었다.
(역시 공식문서가 최고인 것 같다...)

다음 시간엔 서버컴포넌트와 클라이언트를 나눠 리팩토링 할 부분이 남아있는지 찾아보며 Next.js 프로젝트를 성능최적화를 해보겠다.


0. Ref

 How are Server Components rendered?

Benefits of Server Rendering