[Next.js] App Router에서 Client Component하위에 Server Component렌더링하기
일반적으로 Client Component에서 Server Component를 직접 호출할 수 없습니다.
그림으로 나타내면 아래와 같은 구조가 될 겁니다.
코드로 나타내면 아래와 같은 형태이구요.
"use client";
import { useEffect } from "react";
import ServerComponent from "./server-component";
export default function ClientComponent() {
useEffect(() => {
console.log("Client Component mounted");
}, []);
return (
<div>
Client Component
<ServerComponent />
</div>
);
}
서버 컴포넌트의 코드는 아래와 같습니다.
console.log는 실제로 서버에서만 찍혀야겠지만,
export default function ServerComponent() {
console.log("Server Component mounted");
return <div>Server Component</div>;
}
브라우저에서도 찍힌다는 것을 볼 수 있습니다.
어찌 보면 당연한 결과입니다.
ClientComponent는 hydration 이후에 브라우저에서도 실행되는데,
ClientComponent가 ServerComponent를 렌더링하고 있으니까 말이죠.
이해를 돕기위해 보면, ServerComponent라는 함수가 ClientComponent라는 함수안에서 실행되게끔 코드가 짜여진 것 입니다.
"use client";
import { useEffect } from "react";
import ServerComponent from "./server-component";
export default function ClientComponent() {
useEffect(() => {
console.log("Client Component mounted");
}, []);
return (
<div>
Client Component
{ServerComponent()}
</div>
);
}
서버 컴포넌트에서만 동작하는 기능을 사용해도,
const fetchRandom = async () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Math.random());
}, 1000);
});
};
export default async function ServerComponent() {
const data = await fetchRandom();
console.log(data);
return <div>Server Component</div>;
}
아래와 같은 에러를 만나게 됩니다.
이 때, 아래와 같이 children으로 Server Component를 받게 된다면,
"use client";
import { useEffect } from "react";
interface ClientComponentWithChildrenProps {
children: React.ReactNode;
}
export default function ClientComponentWithChildren({
children,
}: ClientComponentWithChildrenProps) {
useEffect(() => {
console.log("Client Component mounted");
}, []);
return <div>Client Component With Children{children}</div>;
}
import ClientComponentWithChildren from "@/components/client-component-with-children";
import ServerComponent from "@/components/server-component";
export default function Home() {
return (
<ClientComponentWithChildren>
<ServerComponent />
</ClientComponentWithChildren>
);
}
실행되는 코드는 아니지만, 이해를 돕기위해 보면 아래와 같은 형태가 되겠죠?
"use client";
import { useEffect } from "react";
import ServerComponent from "./server-component";
interface ClientComponentWithChildrenProps {
children: React.ReactNode;
}
const ServerComponentRender = ServerComponent();
export default function ClientComponentWithChildren({
children,
}: ClientComponentWithChildrenProps) {
useEffect(() => {
console.log("Client Component mounted");
}, []);
return <div>Client Component With Children{ServerComponentRender}</div>;
}
브라우저에서 확인해 보면, Server Component가 서버에서만 실행되는 것을 볼 수 있습니다.
ServerComponent라는 함수를 ClientComponent안에서 직접 호출하는게 아닌
함수를 호출한 결과(jsx)가 children으로 들어오기 때문입니다.
서버에서 렌더링된 것과는 별개로 react의 virtual dom이나 상태 관리 등을 위해
서버에서 렌더링된 jsx는 hydration이후에 리액트의 라이프사이클에 사용될 수 있도록 json의 형태로 클라이언트에 전달됩니다.
https://github.com/Lee-Minhoon/blog-examples/tree/main/ssr-test