리액트18부터, 서버 컴포넌트를 지원 하면서 next.js에서 서버 컴포넌트와 클라이언트 컴포넌트를 섞어 앱을 구성하여 기존 csr이나 ssr보다 더 나은 성능으로 렌더링 할 수 있게 한다. 이 전에는 페이지 단위로 csr, ssr이 정해졌지만 이제는 컴포넌트 단위로 렌더링 방식을 결정할 수 있게 된 것이다.
컴포넌트 단위로 렌더링 방식을 정할 수 있다보니 위 그림 처럼 서버 컴포넌트 안에 클라이언트 컴포넌트를 넣어서, 상호작용이 필요한 컴포넌트들만 csr로 넘겨줄 수 있다.
서버 컴포넌트를 사용하면 이전 글에서 설명 했던 것과 같이 렌더링과 SEO에서 이점이 있다. 또한 Next.js에서 제공하는 App Router의 fetch
함수를 사용하면, 데이터를 서버에서 캐싱하고 revalidate하는 기능들을 사용하며 데이터를 더 효율적으로 관리 할 수 있다. 또한 컴포넌트 코드를 서버에서 관리하기 때문에 번들이 cacheable, predictable해지며 유저와 상호작용해야하는 자바 스크립트 코드가 추가 되지 않는 한, 컴포넌트가 커지더라도 앱 크기가 커지지 않는 장점이 있다.
Next.js의 App Router를 사용하면 모든 컴포넌트가 서버 컴포넌트로 기본설정 되기 때문에 클라이언트 컴포넌트를 사용하려면 별도의 명시가 필요하다.
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
임포트 위에 'use client'
를 입력하면 컴포넌트를 간단하게 클라이언트 컴포넌트로 사용할 수 있다. (클라이언트 컴포넌트더라도 react에서와 달리 서버에서 렌더링 된 상태로 클라이언트에 넘어간 뒤 hydration이 일어난다.)
'use client'
로 컴포넌트를 클라이언트 컴포넌트로 만들 고 난뒤, 아래에 임포트 하는 모든 컴포넌트들은 클라이언트 컴포넌트로 간주 된다.
이러한 이유 때문에 Next.js는 'use client'
아래에 서버 컴포넌트를 임포트 하는것을 지원하지 않는다.
서버 컴포넌트를 클라이언트 컴포넌트 안에서 보여주고 싶다면 클라이언트 컴포넌트에 prop으로 넘겨 줘야 한다.
'use client'
import { useState } from 'react'
export default function ExampleClientComponent({
children, // server component
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children} //server component
</>
)
}
단, 상위의 서버 클라이언트에서는 하위에 표시할 서버 컴포넌트와 클라이언트 컴포넌트 모두 임포트 하여 사용할 수 있다.
// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ExampleClientComponent from './example-client-component'
import ExampleServerComponent from './example-server-component'
// Pages in Next.js are Server Components by default
export default function Page() {
return (
<ExampleClientComponent>
<ExampleServerComponent />
</ExampleClientComponent>
)
}
사실 위와 같이 children
prop으로 넘겨주는 패턴은 리액트에서도 흔히 활용되는 패턴이며 layouts과 pages를 구성하면서 이미 경험했던 방식이다. 또한 위와 같은 방식을 사용하면 부모 (client)컴포넌트의 역할은 오직 넘겨받은children
을 적당한 위치에 위치 시키는 일 뿐이므로 컴포넌트 간 독립성을 유지할 수 있게 한다.