Skip to main content

서버 컴포넌트와 클라이언트 컴포넌트 조합 방법

이 페이지에서는 서버 컴포넌트와 클라이언트를 어떻게 역할을 나누고 조합해서 사용할 수 있을지 알아보겠습니다.

서버 컴포넌트와 클라이언트 컴포넌트 비교

먼저 서버 컴포넌트와 클라이언트 컴포넌트를 비교하여 표로 나타내면 다음과 같습니다.

해야 할 일서버 컴포넌트클라이언트 컴포넌트
데이터 가져오기
백엔드 자원에 (직접) 접근하기
서버에 민감한 정보 유지하기 (접근 토큰, API 키 등)
서버에 큰 종속성 유지하기 / 클라이언트 측 자바스크립트 줄이기
상호작용성 및 이벤트 리스너 추가하기 ( onClick(), onChange(), 등)
상태 및 생명주기 효과 사용하기 (useState(), useReducer(), useEffect(), 등)
브라우저 전용 API 사용하기
상태, 효과 또는 브라우저 전용 API에 의존하는 사용자 정의 훅 사용하기
React 클래스 컴포넌트 사용하기

서버 컴포넌트 패턴(가이드?)

서버 컴포넌트를 사용할 때 알아야 할 몇 가지입니다.

컴포넌트 간 데이터 공유

여러 컴포넌트에서 같은 데이터를 호출해야 하는 경우 Context와 같은 API를 사용하는게 아니라 넥스트의 fetch API를 이용하여 호출합니다. 이렇게 되면 중복 호출처럼 보이지만 실제로는 넥스트 내부적으로 메모이제이션을 해줘서 중복 호출이 되지 않습니다.

참고 : Context는 어차피 서버 컴포넌트에서 지원하는 API도 아닙니다. 클라이언트 컴포넌트에서만 지원됩니다.

클라이언트 컴포넌트에 민감한 정보 포함하지 않기

서버 컴포넌트와 클라이언트 컴포넌트의 경계를 잘 모를때는 실수로 API 키나, 인증 토큰 같은 민감한 정보를 클라이언트 컴포넌트에 포함시킬 수도 있으니 조심해야 합니다. 다음 코드를 봅시다.

export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})

return res.json()
}

이 함수는 API_KEY라는 API 호출에 필요한 인증 값을 포함하고 있습니다. 따라서 이 함수는 클라이언트 컴포넌트에서 사용되면 안됩니다. 이런 인증 값들이 클라이언트 컴포넌트에 노출되지 않도록 주의해야 합니다.

만약, 시스템적으로 강제하고 싶다면 server-only 라는 NPM 패키지를 설치해서 파일 상단에 디렉티브를 선언하여 방지할 수도 있습니다.

npm install server-only
import 'server-only'

export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})

return res.json()
}

이렇게 라이브러리(NPM 패키지)를 설치하고 디렉티브를 선언해 두면 getData() 함수가 클라이언트 컴포넌트에서 임포트되어 사용되었을 때 빌드 시점에 에러를 확인할 수 있습니다.

외부 라이브러리 사용시 주의 사항

넥스트의 서버 컴포넌트는 리액트의 서버 컴포넌트 기술을 기반으로 한 개념입니다. 그러다보니 리액트 서버 컴포넌트가 새로 나온 기능인 탓에 리액트 생태계의 대부분 라이브러리들은 이제서야 use client 라는 디렉티브를 추가하기 시작했습니다. 그리고, useStateuseEffect, 이벤트 핸들러 등이 모두 넥스트 클라이언트 컴포넌트에서만 동작하기 때문에 라이브러리들은 대부분 서버 컴포넌트에서 실행이 되질 않습니다.

acme-carousel 라는 라이브러리가 있다고 합시다. 이 라이브러리는 클라이언트 컴포넌트에서 동작하지만 서버 컴포넌트에서는 다음과 같이 동작하지 않습니다.

import { Carousel } from 'acme-carousel'

export default function Page() {
return (
<div>
<p>View pictures</p>

{/* Error: `useState` can not be used within Server Components */}
<Carousel />
</div>
)
}

왜냐면 캐러우셀 라이브러리는 클라이언트 컴포넌트에서만 동작될 수 있는 state, hook 등이 포함되어 있을 것이기 때문이죠. 만약 그럼에도 불구하고 이 라이브러리를 서버 컴포넌트에서 쓰고 싶다면 다음과 같이 use client 디렉티브를 포함시켜서 다시 모듈로 감쌀 수 있습니다.

'use client'

import { Carousel } from 'acme-carousel'

export default Carousel

이렇게 하고 다시 서버 컴포넌트에서 Carousel를 사용하면 정상적으로 동작합니다.

import Carousel from './carousel'

export default function Page() {
return (
<div>
<p>View pictures</p>

{/* Works, since Carousel is a Client Component */}
<Carousel />
</div>
)
}

클라이언트 컴포넌트 사용 가이드(공식 가이드는 패턴이라고 되어 있음)

클라이언트 컴포넌트는 최대한 컴포넌트 트리 아래 깊숙히 배치

특정 컴포넌트를 'use client'를 이용해서 클라이언트 컴포넌트로 지정하면 그 하위에 있는 컴포넌트들은 모두 클라이언트가 됩니다. 따라서, 최대한 이 클라이언트 컴포넌트의 시작점을 컴포넌트 트리의 맨 아래에 배치하는게 좋습니다. 가능한한 컴포넌트들을 모두 서버 컴포넌트로 지정하라는 말이죠.

하나 예를 들면, 레이아웃에 간단한 UI가 있더라도 이 UI는 클라이언트로 구분하여 임포트하는 것이 좋습니다. 그래야 레이아웃 컴포넌트는 서버 컴포넌트로 동작을 할테니까요.

// SearchBar is a Client Component
import SearchBar from './searchbar'
// Logo is a Server Component
import Logo from './logo'

// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<SearchBar />
</nav>
<main>{children}</main>
</>
)
}

아래부터..

https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#interleaving-server-and-client-components