기존의 page router 방식에서 사전렌더링 과정은 위와 같이 서버 측에서 JS 실행(렌더링), 자바스크립트 코드(React 컴포넌트)를 HTML으로 변환하여 화면에 렌더링 후
화면과 상호작용을 위해 Next 서버가 브라우저에 후속으로 모든 자바스크립트 코드 즉 리액트 코드를 번들링 해서 브라우저에 보내는 하이드레이션 과정을 통해 상호작용이 가능토록 하는 사전 렌더링 방식을 지녔다.
그러나 이런 방식은 다소 비효율적일 수 있는데
그 이유는 Page Router의 경우는 한 페이지 내에 코드를 onClick 같은 브라우저에서 하이듀레이션 해주어야 하는 컴포넌트(상호작용이 있는 컴포넌트) 뿐만 아니라 상호작용이 없는 컴포넌트 또한 JS Bundle 를 통해 전달해주기 때문이다.
그래서 전달되는 JS Bundle의 크기가 크면 TTI 의 시간이 길어질 수 있다는 단점이 있다.
React Server Component
Server Component는 애플리케이션의 서버 부분에서 렌더링되는 컴포넌트로 React 18부터 추가된 Server Component는 Next.js v13 이후로 추가되어 App Router라는 라우팅 방식을 사용할 수 있게 되었다.
App Router에서는 Client와 Server의 컴포넌트를 구분을 지어 실행하는 시점을 지정할 수 있다.
그래서 App Router를 채용한다면 Client와 Server 컴포넌트를 나눌 수가 있고 JS는 번들을 Page에 속한 JS를 전부 번들하는 것이 아닌 스플리팅을 하이듀레이션에 필요한 소스코드만 전달하게되어 더 경량화 된 번들을 전달 할 수 있다.
위 그림과 같이 App Router 방식에서는 서버측에서 JS 실행은 서버와 클라이언트 코드가 같이 렌덩링 된 후, 하이드레이션 과정에서는 클라이언트 컴포넌트만 JS Bundle 하여 전달하기 때문에 Bundle 의 크기가 경량화 되어 전달된다.
간단하게 정리하면 서버 컴포넌트는 서버측에서 사전 렌더링을 진행할 때 딱 한번만 실행되고
클라이언트 컴포넌트는 사전 렌더링 때 한번, 하이드레이션 진행할 때 한번 총 2번이 실행된다.
또한 서버 컴포넌트는 서버에서 직접 데이터를 패칭하고, 그 데이터를 기반으로 HTML을 렌더링한 뒤 클라이언트로 전달한다.
이렇게 하면 여러 컴포넌트에 props를 전달할 필요 없이 서버에서 필요한 데이터를 바로 컴포넌트에서 사용하여 이를 통해 중간 컴포넌트를 거치지 않고 필요한 데이터를 직접 처리할 수 있으므로 props 드릴링을 자연스럽게 해결할 수 있다는 장점이 있다,
그리고 Next.js의 App Router에서는 서버 컴포넌트에서 fetch를 사용할 때 자동 캐싱이 지원된다. 즉, fetch를 호출할 때 별도의 설정을 통해 캐싱 전략을 설정할 수 있다.
서버 컴포넌트와 클라이언트 컴포넌트 사용하기
서버 컴포넌트는 따로 만들어 줄 필요는 없다, 그 이유는 App Router 에서는 기본적으로 서버 컴포넌트로 작용하기 때문이다.
서버 컴포넌트는 서버에서 동작하기 때문에 데이터 패칭 같이 서버 측에서 동작하는 것들은 사용이 가능하지만 반대로 브라우저 측에서 동작하는 useEffect 같은 리액트 Hooks는 사용할 수 없다.
그래서 이러한 브라우저 측에서 사용되는 코드를 사용하고 싶으면 클라이언트 컴포넌트로 변경해주면 되는데, 그 방법은 간단하다.
"use client"
import { useEffect } from "react";
import styles from "./page.module.css";
export default function Home() {
useEffect(()=>{});
return (
<div className={styles.page}>
인덱스 페이지
</div>
);
}
이런식으로 "use client" 문자열만 적어주면 클라이언트 컴포넌트로 변경된다.
아래의 표는 서버 컴포넌트와 클라이언트 컴포넌트를 사용해야 하는 경우를 구분 지었다.(출처)
즉, 상호작용이 필요하면 클라이언트 컴포넌트로 그렇지 않으면 서버 컴포넌트로 만들어 주면 된다.
리액트 서버 컴포넌트 사용시 주의사항
1. 서버 컴포넌트에는 브라우저에서 실행될 코드가 포함되면 안된다.
// src/app/page.tsx
export default function Home(){
const [state, setState] = useState("");
useEffect(()=>{});
return <div>인덱스 페이지</div>;
}
서버 컴포넌트는 서버 측에서만 실행되기 때문에 브라우저 측에서 동작할 코드를 작성하면 오류를 발생한다.
2. 클라이언트 컴포넌트는 서버에서도 실행된다.
서버컴포넌트는 서버에서만 실행이 되지만, 클라이언트 코드는 서버에서도 실행된다.
JS로 사전랜더링이 필요한 HTML을 생성하여 클라이언트로 전달하는데 클라이언트 컴포넌트도 예외 사항이 아니다.
클라이언트 컴포넌트에서도 사전 랜더링 할 수 있는 부분을 HTML로 미리 사전 랜더링을 하여 전달 해야 하기 떄문에 클라이언트에서도 실행하고 전달한다.
TEST해보고 싶다면 console.log 찍어보면 터미널과 브라우저 콘솔에서 두번 찍히는 것을 볼 수 있다.
3. 직접적으로 클라이언트 컴포넌트에서는 서버컴포넌트를 Import 할수 없다.
이 제한은 서버와 클라이언트 간의 역할을 명확하게 구분하기 위해 존재한다.
서버 컴포넌트는 서버에서만 실행되는 코드이기 때문에, 브라우저에서 동작하는 클라이언트 컴포넌트에서 이를 불러오는 것은 불가능하다.
예를 들어, 데이터베이스 요청이나 API 통신처럼 서버 전용 작업을 처리하는 서버 컴포넌트는 클라이언트에서 직접 접근할 수 없으며, 이를 분리된 영역에서 실행해야 하기 때문이다.
// 클라이언트 컴포넌트
"use client"
import { ReactNode } from "react"
export default function ClientComponent({
children
}: {
children : ReactNode
}){
console.log("클라이언트 컴포넌트!")
return (
<div>{children}</div>
)
}
// 서버 클라이언트
export default function ServerComponent(){
console.log("서버 컴포넌트!")
return (
<div></div>
)
}
import ClientComponent from "../../component/client-component";
import styles from "./page.module.css";
import ServerComponent from "../../component/server-component";
export default function Home() {
return (
<div className={styles.page}>
인덱스 페이지
<ClientComponent>
<ServerComponent />
</ClientComponent>
</div>
);
}
다만, 클라이언트 컴포넌트에서 서버 컴포넌트를 import가 아닌 children으로 감싸는 형식으로 사용은 가능하다.
이 방식은 children으로 서버컴포넌트를 전달하여 서버에서 랜더링 시킬 수 있다.
부분적으로 서버 컴포넌트를 이용하여 데이터를 받아오고 이를 클라이언트에서 하이듀레이션하여 사용 할 때 사용하는 방식이다.
4. 서버 컴포넌트에서 클라이언트 컴포넌트에게 직렬화 되지 않는 Props는 전달이 불가하다.
서버 컴포넌트는 서버에서만 렌더링되며, 클라이언트에게는 HTML과 필요한 최소한의 JavaScript만 전송됩니다.
이 컴포넌트는 클라이언트 측에서 실행되지 않으며, 클라이언트에 전송되는 데이터를 직렬화하여 전송합니다.
이를 React Server Component Payload(RSC Payload)라고 하며, 이는 특수한 데이터 형식으로 변환(직렬화)한다.
RSC Payload에는 서버에서 렌더링된 결과물과 클라이언트에서 필요한 거의 모든 정보가 포함되어 있다.
Next.js는 서버에서 생성된 RSC Payload와 클라이언트 컴포넌트 자바스크립트 명령을 사용하여 최종적으로 HTML을 렌더링한다.
그러나 JavaScript 함수, 클래스 인스턴스, DOM 요소, 날짜 객체(Date), Map, Set 구조는 직렬화가 불가능하기 때문에 RSC Payload에 포함 될 수 없어, 서버 컴포넌트에서 클라이언트 컴포넌트로 향하는 Props 가 될 수 없다.
출처 : [한 입 크기로 잘라먹는 Next.js(15+) 강의]
'Next.js' 카테고리의 다른 글
NextJs : 앱 라우터의 데이터 페칭 (0) | 2024.09.26 |
---|---|
NextJs : App Router 네비게이팅 (0) | 2024.09.25 |
NextJS : App Router & Layout (0) | 2024.09.23 |
NextJS : SEO 설정하기 (3) | 2024.09.20 |
NextJs : ISR 사전 렌더링 (0) | 2024.09.20 |