본문 바로가기
웹/프론트엔드

[Next.js] App Router에서 Client Component하위에 Server Component렌더링하기

by 이민훈 2024. 10. 27.

일반적으로 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

 

blog-examples/ssr-test at main · Lee-Minhoon/blog-examples

Contribute to Lee-Minhoon/blog-examples development by creating an account on GitHub.

github.com

 

댓글