nextjs에서 서버 컴포넌트와 클라이언트 컴포넌트를 사용할 수 있다. 최상단 app 폴더에서 기본적으로 모든 컴포넌트들은 서버 컴포넌트 이다.
server component는 서버에서 렌더되는 컴포넌트들이다.
위와 같이 페이지를 컴포넌트 단위로 나눠봤을때, 컴포넌트들의 상당수가 인터랙티브 하지 않다. 사용자와의 인터랙션이 없다면 서버 컴포넌트로 렌더링 할 것을 고려할 수 있다.
서버 컴포넌트 코드는 브라우저에 다운로드 되지 않고 서버에서 미리 렌더링된 static content를 전달하기 때문에 패키지를 추가해도 번들 사이즈에 영향을 끼치지 않는다. React 서버 컴포넌트를 사용하면, 컴포넌트를 정기적으로 다시 가져올 수 있다. 새 데이터가 있을 때 리렌더링 되는 컴포넌트가 서버에서 실행되므로 클라이언트에 전송되는 코드의 양을 제한 할 수 있다.
Code splitting : 하나의 거대한 자바스크립트 번들을 여러 개의 작은 번들로 쪼개어 필요할 때마다 클라이언트로 전송하는 방법
서버 컴포넌트에서 import 되는 모든 클라이언트 컴포넌트는 code splitting 포인트로 간주됨.
Client components는 어플리케이션 내에서 클라이언트 사이드 인터랙션을 가능하게 한다.
"use client" 는 클라이언트 컴포넌트를 선언하는 컨벤션이다. 클라이언트 컴포넌트 최상단에 위치한다. 해당 컴포넌트의 자식 컴포넌트들 또한 클라이언트 번들에 포함된다. "use client"가 선언되지 않은 컴포넌트는 디폴트로 서버 컴포넌트가 된다.
'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 case이다.
1. 가능하다면 클라이언트 컴포넌트의 계층은 낮을수록 좋다.
예를 들어, 정적 요소들(로고,링크 등)와 상태가 업데이트되는 인터랙티브한 검색바를 포함하는 레이아웃이 있다고 하자. 이 경우 전체 레이아웃을 클라이언트 컴포넌트로 정의할 것이 아니라, 서버 컴포넌트로 정의된 레이아웃 하위에 인터랙티브한 로직들을 담은 클라이언트 컴포넌트를 자식 컴포넌트로 정의하는 것이다. 즉 레이아웃 전체를 클라이언트 사이드에서 그리지 않는다.
// 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>
</>
);
}
2. 클라이언트 컴포넌트에서 서버 컴포넌트를 임포트 하는 것은 지원되지 않는다.
3. 다만 서버 컴포넌트는 클라이언트 컴포넌트에게 또 다른 서버 컴포넌트를 자식으로 넘겨주는 건 가능
'use client';
import { useState } from 'react';
export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode;
}) {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
);
}
결국 리액트 컴포넌트는 목적에 따라 서버(data fetching),클라이언트(유저 인터랙션)으로 분리 될 수 있다.
서버 코드가 의도치 않게 클라이언트에서 사용되는 것을 방지하기 위해서
server-only 패키지를 사용할 수 있다.
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();
}
위와 같이 server-only 패키지를 임포트 시키면 해당 코드가 클라이언트 컴포넌트에서 임포트되었을때 빌드에러를 실행시킬수 있다. 마찬가지로 client-only 패키지를 사용해 클라이언트사이드에서만 실행되어야 하는 코드(ex.윈도우 객체에 접근하는 경우)가 서버 컴포넌트에서 실행되는 것을 방지할 수 있다.
특별한 이유가 있지 않은 한 data fetching은 서버 컴포넌트에서 이루어지도록 하는 것이 좋다. 이는 더 나은 성능과 사용자 경험을 제공한다.
useState,나 useEffect 등을 사용하여 클라이언트 사이드에서 실행되어야하는 써드파트 패키지의 컴포넌트들의 경우 "use strict" 가 선언되지 않은 옛날 버전의 패키지들이 다수이다. 따라서 서버사이드에서 실행되어 에러가 발생하는 경우 아래와 같이 조치 하면 클라이언트에서 실행되게 하면 에러가 해결된다.
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>
);
}
'use client';
import { Carousel } from 'acme-carousel';
export default Carousel;
그러나 사용자 경험 향상을 위해 함께 사용할 수는 있다.클라이언트 사이드 렌더링을 사용하는 앱의 경우 페이지 진입 시 HTML, 자바스크립트 그리고 모든 데이터가 로드되고 컴포넌트 렌더링이 끝나기 전까지 사용자는 아무런 기능이 없는 빈 화면만 보게 된다. 자바스크립트 번들의 용량이 크거나 사용자의 네트워크 속도가 느릴수록 사용자는 오랜 시간 빈 화면을 보게 되고 이는 사용자 경험을 저하시키게 된다.
반면 Next.js와 같은 서버 사이드 렌더링을 사용하는 애플리케이션은 클라이언트 애플리케이션의 자바스크립트 파일을 서버에서 먼저 HTML로 렌더링 한다. 페이지가 정상적으로 동작하기 위해서는 자바스크립트 번들이 모두 다운로드되고 hydration이 완료되어야 하지만 빈 화면 대신 데이터가 존재하는 HTML을 제공함으로써 무거운 자바스크립트 파일이 다운로드되는 동안 사용자에게 의미 있는 콘텐츠를 제공할 수 있다.
서버 사이드 렌더링의 가장 큰 목적은 non-interactive한 버전의 클라이언트 컴포넌트를 최대한 빠르게 브라우저에 전달하여 초기 페이지의 First Contentful Paint 또는 Largest Contentful Paint 속도를 향상시키는 것이다.
서버 컴포넌트와 서버 사이드 렌더링은 서버에서 렌더링 된다는 유사점이 있지만 해결하고자 하는 문제점이 다르다.
서버 컴포넌트의 코드는 클라이언트로 전달되지 않는다. 하지만 서버 사이드 렌더링의 모든 컴포넌트의 코드는 자바스크립트 번들에 포함되어 클라이언트로 전송된다.
서버 컴포넌트는 페이지 레벨에 상관없이 모든 컴포넌트에서 서버에 접근 가능. 하지만 Next.js의 경우 가장 top level의 페이지에서만 getServerProps()나 getInitialProps()로 서버에 접근 가능.
클라이언트 측의 상태(state)를 유지하면서 서버 컴포넌트를 다시 가져올 수 있다.이는 주요 전송 메커니즘이 HTML보다 훨씬 풍부하기 때문이다. 따라서, 내부 상태(e.g 검색 입력 텍스트, 포커스, 텍스트 선택)를 없애지 않고 서버에서 렌더링 한 부분(e.g 검색 결과 목록)을 다시 가져올 수 있게 한다.
참고
https://nextjs.org/docs/getting-started/react-essentials
https://www.patterns.dev/posts/react-server-components
https://ui.toast.com/weekly-pick/ko_20210119
https://tech.kakaopay.com/post/react-server-components/#:~:text=%EC%84%9C%EB%B2%84%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%8A%94%20%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%20%EC%83%81%ED%83%9C,%EB%A0%8C%EB%8D%94%EB%A7%81%ED%95%98%EC%97%AC%20%EC%A0%84%EB%8B%AC%ED%95%A0%20%EC%88%98%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4.