본문 바로가기
React

Next.js 에센셜: Next.js 웹앱에서 Client Side Rendering을 추구하면 안되는걸까?

by 돈민찌 2023. 5. 17.
반응형

Getting Started: React Essentials | Next.js

An overview of essential React features for building Next.js Applications, including Server Components.


Next.js로 애플리케이션을 빌드하려면 서버 컴포넌트와 같은 React의 최신 기능에 익숙해지는 것이 도움이 됩니다. 이 페이지에서는 서버 컴포넌트와 클라이언트 컴포넌트의 차이점, 사용 시기, 권장 패턴에 대해 설명합니다.

React를 처음 사용한다면 React 문서를 참조하는 것도 좋습니다. 다음은 학습에 도움이 되는 몇 가지 훌륭한 리소스입니다:

서버 컴포넌트

서버 및 클라이언트 컴포넌트를 사용하면 개발자는 서버와 클라이언트를 아우르는 애플리케이션을 구축할 수 있으며, 클라이언트 측 앱의 풍부한 상호작용과 기존 서버 렌더링의 향상된 성능을 결합할 수 있습니다.

서버 컴포넌트에서 생각하기

React가 UI 구축에 대한 사고방식을 바꾼 것과 유사하게, React 서버 컴포넌트는 서버와 클라이언트를 활용하는 하이브리드 애플리케이션을 구축하기 위한 새로운 사고 모델을 소개합니다.

단일 페이지 애플리케이션의 경우처럼 전체 애플리케이션을 클라이언트 측에서 렌더링하는 대신, React는 이제 컴포넌트의 목적에 따라 렌더링할 위치를 선택할 수 있는 유연성을 제공합니다.

예를 들어 애플리케이션의 페이지를 생각해 보세요:

서버 컴포넌트에서 생각하기서버 컴포넌트에서 생각하기

페이지를 더 작은 컴포넌트로 분할하면 대부분의 컴포넌트가 비대화형이며 서버에서 서버 컴포넌트로 렌더링할 수 있다는 것을 알 수 있습니다. 더 작은 인터랙티브 UI의 경우 클라이언트 컴포넌트를 _스프링 인_할 수 있습니다. 이는 Next.js의 서버 우선 접근 방식과 일치합니다.

왜 서버 컴포넌트인가?

그렇다면 왜 서버 컴포넌트일까요? 클라이언트 컴포넌트보다 서버 컴포넌트를 사용하면 어떤 이점이 있을까요?

서버 컴포넌트를 사용하면 개발자가 서버 인프라를 더 잘 활용할 수 있습니다. 예를 들어, 이전에는 클라이언트의 자바스크립트 번들 크기에 영향을 미쳤던 큰 종속성을 전적으로 서버에만 유지할 수 있으므로 성능이 향상됩니다. 서버 컴포넌트를 사용하면 React 애플리케이션을 작성하는 것이 PHP나 Ruby on Rails와 비슷하게 느껴지지만, React의 강력한 성능과 유연성, UI 템플릿을 위한 컴포넌트 모델이 더해집니다.

서버 컴포넌트를 사용하면 초기 페이지 로딩이 빨라지고 클라이언트 측 JavaScript 번들 크기가 줄어듭니다. 기본 클라이언트 측 런타임은 캐시가 가능하고 예측 가능한 크기이며, 애플리케이션이 커져도 증가하지 않습니다. 추가 자바스크립트는 클라이언트 컴포넌트를 통해 애플리케이션에서 클라이언트 측 상호 작용이 사용될 때만 추가 됩니다.

Next.js로 경로가 로드되면 초기 HTML이 서버에서 렌더링됩니다. 그런 다음 이 HTML은 브라우저에서 점진적으로 개선되어 클라이언트가 애플리케이션을 인계받아 Next.js 및 React 클라이언트 측 런타임을 비동기적으로 로드하여 상호작용을 추가할 수 있습니다.

서버 컴포넌트로 쉽게 전환할 수 있도록 앱 라우터 내의 모든 컴포넌트는 특수 파일코로케이션 컴포넌트를 포함하여 기본적으로 서버 컴포넌트입니다. 따라서 별도의 작업 없이 자동으로 적용하여 즉시 뛰어난 성능을 얻을 수 있습니다. 선택적으로 '클라이언트 사용' 지시문을 사용하여 클라이언트 컴포넌트를 옵트인할 수도 있습니다.

클라이언트 컴포넌트

클라이언트 컴포넌트를 사용하면 애플리케이션에 클라이언트 측 인터랙션을 추가할 수 있습니다. Next.js에서는 서버에서는 미리 렌더링되고 클라이언트에서는 하이드레이트됩니다. 클라이언트 컴포넌트는 페이지 라우터의 컴포넌트가 항상 작동하는 방식이라고 생각하면 됩니다.

클라이언트 사용 지시어

"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 client"은 서버 전용 코드와 클라이언트 코드 사이에 위치 합니다. 파일 상단의 임포트 위에 배치하여 서버 전용에서 클라이언트 부분으로 경계를 넘나드는 컷오프 지점을 정의합니다. 파일에 "use client"가 정의되면 하위 컴포넌트를 포함하여 파일로 가져온 다른 모든 모듈은 클라이언트 번들의 일부로 간주됩니다.

서버 컴포넌트가 기본값이므로, "use client" 지시어로 시작하는 모듈에서 정의되거나 가져온 것이 아니라면 모든 컴포넌트는 서버 컴포넌트 모듈 그래프의 일부가 됩니다.

알아두면 좋은 정보:

💎 서버 컴포넌트 모듈 그래프에 있는 컴포넌트는 서버에서만 렌더링되도록 보장됩니다.
클라이언트 컴포넌트 모듈 그래프의 컴포넌트는 주로 클라이언트에서 렌더링되지만, Next.js를 사용하면 서버에서 미리 렌더링한 후 클라이언트에서 하이드레이션할 수도 있습니다.

💎 "use client" 지시문은 임포트하기 전에 파일의 상단에 정의해야 합니다.

💎 모든 파일에 "use client"를 정의할 필요는 없습니다. 클라이언트 모듈 경계는 "진입점"에서 한 번만 정의하면 되며, 이 경계로 가져온 모든 모듈은 클라이언트 컴포넌트로 간주됩니다.

서버 및 클라이언트 컴포넌트는 언제 사용하나요?

서버 컴포넌트와 클라이언트 컴포넌트 사이의 결정을 단순화하기 위해 클라이언트 컴포넌트에 대한 사용 사례가 있을 때까지 서버 컴포넌트(app 디렉터리의 기본값)를 사용하는 것이 좋습니다.

이 표에는 서버 컴포넌트와 클라이언트 컴포넌트의 다양한 사용 사례가 요약되어 있습니다:

     
무엇을 해야 하나요? 서버 컴포넌트 클라이언트 컴포넌트
데이터 가져오기.
백엔드 리소스 액세스(직접)
서버에 민감한 정보(액세스 토큰, API 키 등) 보관
서버에 대한 대규모 종속성 유지 / 클라이언트 측 자바스크립트 감소
상호작용 및 이벤트 리스너 추가(onClick(), onChange() 등)
상태 및 라이프사이클 효과 사용 (useState(), useReducer(), useEffect() 등)
브라우저 전용 API 사용
상태, 효과 또는 브라우저 전용 API에 의존하는 커스텀 훅 사용
React 클래스 컴포넌트 사용

패턴

클라이언트 컴포넌트를 리프로 이동하기

애플리케이션의 성능을 향상시키려면 가능한 경우 클라이언트 컴포넌트를 컴포넌트 트리의 잎으로 이동하는 것이 좋습니다.

예를 들어 정적 요소(예: 로고, 링크 등)가 있는 레이아웃과 상태를 사용하는 대화형 검색창이 있을 수 있습니다.

전체 레이아웃을 클라이언트 컴포넌트로 만드는 대신 인터랙티브 로직을 클라이언트 컴포넌트(예: <SearchBar />)로 이동하고 레이아웃은 서버 컴포넌트로 유지하세요. 즉, 레이아웃의 모든 컴포넌트 자바스크립트를 클라이언트로 보낼 필요가 없습니다.

// SearchBar는 클라이언트 컴포넌트입니다.
import SearchBar from './searchbar';
// 로고는 서버 컴포넌트입니다.
import Logo from './logo';

// 레이아웃은 기본적으로 서버 컴포넌트입니다.
export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <main>{children}</main>
    </>
  );
}

클라이언트 및 서버 컴포넌트 구성하기

서버 컴포넌트와 클라이언트 컴포넌트는 같은 컴포넌트 트리에서 결합할 수 있습니다.

백그라운드에서 React는 다음과 같이 렌더링을 처리합니다:

  • 서버에서 React는 결과를 클라이언트에 보내기 전에 모든 서버 컴포넌트를 렌더링합니다.
    • 여기에는 클라이언트 컴포넌트 안에 중첩된 서버 컴포넌트가 포함됩니다.
    • 이 단계에서 발생하는 클라이언트 컴포넌트는 건너뜁니다.
  • 클라이언트에서 React는 클라이언트 컴포넌트를 렌더링하고 서버 컴포넌트의 렌더링 결과를 슬롯에 삽입하여 서버와 클라이언트에서 수행한 작업을 병합합니다.
    • 서버 컴포넌트가 클라이언트 컴포넌트 안에 중첩된 경우, 렌더링된 콘텐츠는 클라이언트 컴포넌트 안에 올바르게 배치됩니다.

💡알아두면 좋은 정보: Next.js에서는 초기 페이지 로드 시 위 단계의 서버 컴포넌트와 클라이언트 컴포넌트의 렌더링 결과가 모두 서버에서 HTML로 미리 렌더링되어 초기 페이지 로딩 속도가 빨라집니다.

클라이언트 컴포넌트 안에 서버 컴포넌트 중첩

위에 설명한 렌더링 흐름을 고려할 때 서버 컴포넌트를 클라이언트 컴포넌트로 임포트하는 방식은 서버를 추가로 왕복해야 하므로 제한이 있습니다.

지원되지 않는 패턴: 서버 컴포넌트를 클라이언트 컴포넌트에 임포트하기

다음 패턴은 지원되지 않습니다. 서버 컴포넌트를 클라이언트 컴포넌트로 임포트할 수 없습니다:

'use client';

// 이 패턴은 **작동하지** 않습니다!
// 서버 컴포넌트를 클라이언트 컴포넌트로 가져올 수 없습니다.
import ExampleServerComponent from './example-server-component';

export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);

  return (
  <>
    <button onClick={() => setCount(count + 1)}>{count}</button>
    <ExampleServerComponent />
  </>
  );
}
권장하는 패턴: 서버 컴포넌트를 클라이언트 컴포넌트에 프롭으로 전달하기

대신, 클라이언트 컴포넌트를 디자인할 때 React 프로퍼티를 사용하여 서버 컴포넌트에 "구멍" 을 표시할 수 있습니다.

서버 컴포넌트는 서버에서 렌더링되고, 클라이언트 컴포넌트가 클라이언트에서 렌더링될 때 "구멍" 은 서버 컴포넌트의 렌더링 결과로 채워집니다.

일반적인 패턴은 React children 프로퍼티를 사용해 "구멍" 을 만드는 것입니다. 일반적인 children 프로퍼티를 허용하도록 <ExampleClientComponent>를 리팩터링하고 <ExampleClientComponent>의 임포트와 명시적 중첩을 부모 컴포넌트로 옮길 수 있습니다.

'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}
  </>
  );
}

이제 <ExampleClientComponent>children이 무엇인지 전혀 알지 못합니다. 사실, 자식이 결국 서버 컴포넌트의 결과에 의해 채워질 것이라는 사실조차 알지 못합니다.

ExampleClientComponent가 할 수 있는 유일한 책임은 결국 자식이 어디에 위치할지 결정하는 것뿐입니다.

부모 서버 컴포넌트에서 <ExampleClientComponent><ExampleServerComponent>를 모두 임포트하고 <ExampleServerComponent><ExampleClientComponent>의 자식으로 전달할 수 있습니다:

// 이 패턴은 작동합니다:
// 서버 컴포넌트를 클라이언트 컴포넌트의 자식이나 소품으로 전달할 수 있습니다.
// 클라이언트 컴포넌트의 자식이나 프로퍼티로 전달할 수 있습니다.
import ExampleClientComponent from './example-client-component';
import ExampleServerComponent from './example-server-component';

// Next.js의 페이지는 기본적으로 서버 컴포넌트입니다.
export default function Page() {
  return (
  <ExampleClientComponent>
    <ExampleServerComponent />
  </ExampleClientComponent>
  );
}

이 접근 방식을 사용하면 <ExampleClientComponent><ExampleServerComponent>의 렌더링이 분리되어 독립적으로 렌더링될 수 있으며, 클라이언트 컴포넌트보다 먼저 서버에서 렌더링되는 서버 컴포넌트에 맞춰 조정됩니다.

알아두면 좋은 정보

  • 이 패턴은 레이아웃과 페이지children 프로퍼로 이미 적용되어 있으므로 별도의 래퍼 컴포넌트를 만들 필요가 없습니다.
  • React 컴포넌트(JSX)를 다른 컴포넌트에 전달하는 것은 새로운 개념이 아니며 항상 React 컴포지션 모델의 일부였습니다.
  • 이 컴포지션 전략은 서버 컴포넌트와 클라이언트 컴포넌트 모두에서 작동하는데, 소품을 받는 컴포넌트는 소품이 무엇인지 알지 못하기 때문입니다. 단지 전달받은 소품이 어디에 배치되어야 하는지에 대해서만 책임이 있습니다.
  • 전달된 소품이 클라이언트 컴포넌트가 클라이언트에서 렌더링되기 훨씬 전에 서버에서 독립적으로 렌더링될 수 있습니다.
  • 가져온 중첩된 자식 컴포넌트를 다시 렌더링하는 부모 컴포넌트의 상태 변화를 피하기 위해 "콘텐츠 리프팅"이라는 동일한 전략이 사용되었습니다.
  • children 프로퍼티에만 국한되지 않습니다. 모든 프로퍼티를 사용하여 JSX를 전달할 수 있습니다.

서버에서 클라이언트 컴포넌트로 프롭 전달하기(직렬화)

서버에서 클라이언트 컴포넌트로 전달되는 프롭은 직렬화 가능이어야 합니다. 즉, 함수, 날짜 등과 같은 값은 클라이언트 컴포넌트에 직접 전달할 수 없습니다.

네트워크 경계(boundary)는 어디인가요?

앱 라우터에서 네트워크 경계는 서버 컴포넌트와 클라이언트 컴포넌트 사이입니다. 이는 getStaticProps/getServerSideProps와 페이지 컴포넌트 사이에 경계가 있는 페이지와는 다릅니다. 서버 컴포넌트 내부에서 가져온 데이터는 클라이언트 컴포넌트로 전달되지 않는 한 네트워크 경계를 넘지 않으므로 직렬화할 필요가 없습니다. 서버 컴포넌트를 이용한 데이터 불러오기에 대해 자세히 알아보세요.

서버 전용 코드를 클라이언트 컴포넌트에서 제외하기(포이즈닝)

자바스크립트 모듈은 서버와 클라이언트 컴포넌트 모두에서 공유할 수 있기 때문에 서버에서만 실행되어야 하는 코드가 클라이언트로 몰래 들어갈 수 있습니다.

예를 들어 다음 데이터 불러오기 함수를 살펴봅시다:

export async function getData() {
  const res = await fetch('https://external-service.com/data', {
  headers: {
    authorization: process.env.API_KEY,
  },
  });

  return res.json();
}

언뜻 보기에는 getData가 서버와 클라이언트 모두에서 작동하는 것처럼 보입니다. 하지만 환경 변수 API_KEY 앞에 NEXT_PUBLIC이 붙지 않았기 때문에 서버에서만 액세스할 수 있는 비공개 변수입니다. Next.js는 보안 정보 유출을 방지하기 위해 클라이언트 코드에서 비공개 환경 변수를 빈 문자열로 대체합니다.

따라서 클라이언트에서 getData()를 가져와서 실행할 수 있지만 예상대로 작동하지 않습니다. 또한 변수를 공개하면 클라이언트에서 함수가 작동하지만 민감한 정보가 유출될 수 있습니다.

따라서 이 함수는 서버에서만 실행되도록 의도하여 작성되었습니다.

서버 전용 패키지

이러한 종류의 의도하지 않은 클라이언트 서버 코드 사용을 방지하기 위해 server-only 패키지를 사용하여 다른 개발자가 실수로 이러한 모듈 중 하나를 클라이언트 컴포넌트로 임포트하는 경우 빌드 타임 오류를 발생시킬 수 있습니다.

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();
}

이제 getData()를 임포트하는 모든 클라이언트 컴포넌트는 이 모듈이 서버에서만 사용할 수 있음을 설명하는 빌드 타임 오류를 받게 됩니다.

해당 패키지 client-only를 사용하여 클라이언트 전용 코드(예: window 객체에 액세스하는 코드)가 포함된 모듈을 표시할 수 있습니다.

데이터 불러오기

클라이언트 컴포넌트에서 데이터를 불러오는 것도 가능하지만, 클라이언트에서 데이터를 불러와야 하는 특별한 이유가 없다면 서버 컴포넌트에서 데이터를 불러오는 것을 권장합니다. 데이터 불러오기를 서버로 옮기면 성능과 사용자 경험이 향상됩니다.

데이터 가져오기에 대해 자세히 알아보기.

서드파티 패키지

서버 컴포넌트는 새로운 것이기 때문에, 에코시스템의 서드파티 패키지는 이제 막 useState, useEffect, createContext와 같은 클라이언트 전용 기능을 사용하는 컴포넌트에 "use client" 지시어를 추가하기 시작했습니다.

오늘날, 클라이언트 전용 기능을 사용하는 npm 패키지의 많은 컴포넌트에는 아직 이 지시어가 없습니다. 이러한 서드파티 컴포넌트는 "use-client" 지시어를 가지고 있기 때문에 자체 클라이언트 컴포넌트 내에서 예상대로 작동하지만 서버 컴포넌트 내에서는 작동하지 않습니다.

예를 들어 <AcmeCarousel /> 컴포넌트가 있는 가상의 acme-carousel 패키지를 설치했다고 가정해 봅시다. 이 컴포넌트는 useState를 사용하지만 아직 사용 클라이언트 지시문을 가지고 있지 않습니다.

클라이언트 컴포넌트 내에서 <AcmeCarousel />을 사용하면 예상대로 작동합니다:

'use client';

import { useState } from 'react';
import { AcmeCarousel } from 'acme-carousel';

export default function Gallery() {
  let [isOpen, setIsOpen] = useState(false);

  return (
  <div>
    <button onClick={() => setIsOpen(true)}>View pictures</button>
    {/* 🟢 Works, since AcmeCarousel is used within a Client Component */}
    {isOpen && <AcmeCarousel />}
  </div>
  );
}

그러나 서버 컴포넌트 내에서 직접 사용하려고 하면 오류가 표시됩니다:

import { AcmeCarousel } from 'acme-carousel';

export default function Page() {
  return (
  <div>
    <p>View pictures</p>
    {/* 🔴 Error: `useState` can not be used within Server Components */}
    <AcmeCarousel />
  </div>
  );
}

이는 Next.js가 <AcmeCarousel />이 클라이언트 전용 기능을 사용하고 있다는 사실을 알지 못하기 때문입니다.

이 문제를 해결하려면 클라이언트 전용 기능에 의존하는 타사 컴포넌트를 자체 클라이언트 컴포넌트로 래핑하면 됩니다:

'use client';

import { AcmeCarousel } from 'acme-carousel';

export default AcmeCarousel;

Now, you can use <Carousel /> directly within a Server Component:

import Carousel from './carousel';

export default function Page() {
  return (
  <div>
    <p>View pictures</p>
    {/*  Works, since Carousel is a Client Component */}
    <Carousel />
  </div>
  );
}

대부분의 서드파티 컴포넌트는 클라이언트 컴포넌트 내에서 사용할 가능성이 높기 때문에 래핑할 필요가 없을 것으로 예상합니다. 그러나 한 가지 예외는 공급자 컴포넌트인데, 이는 React 상태와 컨텍스트에 의존하고 일반적으로 애플리케이션의 루트에 필요하기 때문입니다. 아래에서 서드파티 컨텍스트 프로바이더에 대해 자세히 알아보기.

  • 비슷한 방식으로, 다른 개발자가 사용할 패키지를 만드는 라이브러리 작성자는 "use-client" 지시문을 사용하여 패키지의 클라이언트 진입점을 표시할 수 있습니다. 이렇게 하면 패키지 사용자가 래핑 경계를 만들지 않고도 패키지 컴포넌트를 서버 컴포넌트로 직접 가져올 수 있습니다.
  • 트리에서 'use client' 더 깊숙이를 사용하여 가져온 모듈이 서버 컴포넌트 모듈 그래프의 일부가 되도록 함으로써 패키지를 최적화할 수 있습니다.
  • 일부 번들러는 "use-client" 지시문을 제거할 수 있다는 점에 유의할 필요가 있습니다. "use client"` 지시어를 포함하도록 esbuild를 구성하는 예제는 React Wrap BalancerVercel Analytics 리포지토리에서 찾을 수 있습니다.

컨텍스트

대부분의 React 애플리케이션은 컴포넌트 간에 데이터를 공유하기 위해 createContext를 통해 직접 또는 서드파티 라이브러리에서 가져온 공급자 컴포넌트를 통해 간접적으로 context에 의존합니다.

Next.js 13에서 컨텍스트는 클라이언트 컴포넌트 내에서 완벽하게 지원되지만, 서버 컴포넌트 내에서 직접 생성하거나 사용할 수는 없습니다. 서버 컴포넌트에는 (인터랙티브하지 않기 때문에) React state가 없고, 컨텍스트는 주로 일부 React state가 업데이트된 후 트리 깊숙한 곳에 있는 인터랙티브 컴포넌트를 다시 렌더링하는 데 사용되기 때문입니다.

서버 컴포넌트 간에 데이터를 공유하기 위한 대안은 나중에 논의하겠지만, 먼저 클라이언트 컴포넌트 내에서 컨텍스트를 사용하는 방법을 살펴봅시다.

클라이언트 컴포넌트에서 컨텍스트 사용하기

모든 컨텍스트 API는 클라이언트 컴포넌트 내에서 완벽하게 지원됩니다:

'use client';

import { createContext, useContext, useState } from 'react';

const SidebarContext = createContext();

export function Sidebar() {
  const [isOpen, setIsOpen] = useState();

  return (
  <SidebarContext.Provider value={{ isOpen }}>
    <SidebarNav />
  </SidebarContext.Provider>
  );
}

function SidebarNav() {
  let { isOpen } = useContext(SidebarContext);

  return (
  <div>
    <p>Home</p>
    {isOpen && <Subnav />}
  </div>
  );
}

그러나 컨텍스트 공급자는 일반적으로 현재 테마와 같은 글로벌 관심사를 공유하기 위해 애플리케이션의 루트 근처에 렌더링됩니다. 컨텍스트는 서버 컴포넌트에서 지원되지 않으므로 애플리케이션의 루트에서 컨텍스트를 만들려고 하면 오류가 발생합니다:

import { createContext } from 'react';

// 서버 컴포넌트에서는 createContext가 지원되지 않습니다.
export const ThemeContext = createContext({});

export default function RootLayout({ children }) {
  return (
  <html>
    <body>
      <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
    </body>
  </html>
  );
}

이 문제를 해결하려면 컨텍스트를 생성하고 클라이언트 컴포넌트 내에서 해당 프로바이더를 렌더링하세요:

'use client';

import { createContext } from 'react';

export const ThemeContext = createContext({});

export default function ThemeProvider({ children }) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>;
}

이제 서버 컴포넌트가 클라이언트 컴포넌트로 표시되었으므로 공급자를 직접 렌더링할 수 있습니다:

import ThemeProvider from './theme-provider';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
  <html>
    <body>
      <ThemeProvider>{children}</ThemeProvider>
    </body>
  </html>
  );
}

프로바이더가 루트에서 렌더링되면 앱의 다른 모든 클라이언트 컴포넌트가 이 컨텍스트를 사용할 수 있습니다.

참고: 프로바이더는 트리에서 가능한 한 깊숙이 렌더링해야 합니다. '테마 프로바이더'가 전체 <html> 문서 대신 {children}만 래핑하는 방식을 주목하세요. 이렇게 하면 Next.js가 서버 컴포넌트의 정적 부분을 더 쉽게 최적화할 수 있습니다.

서버 컴포넌트에서 서드파티 컨텍스트 공급자 렌더링

서드파티 npm 패키지에는 애플리케이션의 루트 근처에서 렌더링해야 하는 프로바이더가 포함되어 있는 경우가 많습니다. 이러한 프로바이더에 "use-client" 지시어가 포함된 경우, 서버 컴포넌트 내부에서 직접 렌더링할 수 있습니다. 그러나 서버 컴포넌트는 매우 새롭기 때문에 많은 서드파티 공급자가 아직 이 지시문을 추가하지 않았을 것입니다.

"use client"가 없는 서드파티 공급자를 렌더링하려고 하면 오류가 발생합니다:

import { ThemeProvider } from 'acme-theme';

export default function RootLayout({ children }) {
  return (
  <html>
    <body>
      {/*  Error: `createContext` can't be used in Server Components */}
      <ThemeProvider>{children}</ThemeProvider>
    </body>
  </html>
  );
}

이 문제를 해결하려면 타사 제공업체를 자체 클라이언트 컴포넌트로 래핑하세요:

'use client';

import { ThemeProvider } from 'acme-theme';
import { AuthProvider } from 'acme-auth';

export function Providers({ children }) {
  return (
  <ThemeProvider>
    <AuthProvider>{children}</AuthProvider>
  </ThemeProvider>
  );
}

이제 루트 레이아웃 내에서 바로 <Provider />를 가져와 렌더링할 수 있습니다.

import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
  <html>
    <body>
      <Providers>{children}</Providers>
    </body>
  </html>
  );
}

루트에서 렌더링된 프로바이더를 사용하면 이러한 라이브러리의 모든 컴포넌트와 후크가 자체 클라이언트 컴포넌트 내에서 예상대로 작동합니다.

타사 라이브러리가 클라이언트 코드에 "use-client"를 추가하면 래퍼 클라이언트 컴포넌트를 제거할 수 있습니다.

서버 컴포넌트 간 데이터 공유하기

서버 컴포넌트는 인터랙티브하지 않으므로 React 상태에서 읽지 않으므로 데이터를 공유하기 위해 컨텍스트의 모든 기능이 필요하지 않습니다. 여러 서버 컴포넌트가 액세스해야 하는 공통 데이터가 있는 경우 모듈 범위 내에서 전역 싱글톤과 같은 네이티브 자바스크립트 패턴을 사용할 수 있습니다.

예를 들어 모듈을 사용해 여러 컴포넌트에서 데이터베이스 연결을 공유할 수 있습니다:

export const db = new DatabaseConnection();
import { db } from '@utils/database';

export async function UsersLayout() {
  let users = await db.query();
  // ...
}
import { db } from '@utils/database';

export async function DashboardPage() {
  let user = await db.query();
  // ...
}

위의 예시에서는 레이아웃과 페이지 모두 데이터베이스 쿼리를 수행해야 합니다. 이러한 각 컴포넌트는 @utils/database 모듈을 가져와서 데이터베이스에 대한 접근 권한을 공유합니다.

서버 컴포넌트 간 가져오기 요청 공유

데이터를 불러올 때 Page 또는 LayOut과 일부 하위 컴포넌트 간에 fetch 결과를 공유하고 싶을 수 있습니다. 이는 컴포넌트 간의 불필요한 결합이며, 컴포넌트 간에 property로 충분히 앞뒤로 전달할 수 있습니다.

대신 데이터를 소비하는 컴포넌트와 함께 데이터 가져오기를 배치하는 것이 좋습니다. 서버 컴포넌트에서 fetch 요청은 자동으로 중복 제거되므로 각 경로 세그먼트는 중복 요청에 대한 걱정 없이 필요한 데이터를 정확하게 요청할 수 있습니다. Next.js는 fetch 캐시에서 동일한 값을 읽습니다.

출처 : Get Started: React Essentials | Next.js 공식문서
 

Getting Started: React Essentials | Next.js

To build applications with Next.js, it helps to be familiar with React's newer features such as Server Components. This page will go through the differences between Server and Client Components, when to use them, and recommended patterns. If you're new to

nextjs.org

잠깐!!! ✋🏼✋🏼✋🏼

이해가 어려우신 분들을 위해 요약 한번 하고 끝내겠습니다.
  • 💻 개발자는 서버 컴포넌트클라이언트 컴포넌트를 사용하여 클라이언트 측 앱의 상호작용과 기존 서버 렌더링의 성능 향상을 결합한 애플리케이션을 구축할 수 있습니다.
  • 💡 React 서버 컴포넌트는 서버와 클라이언트를 활용하는 하이브리드 애플리케이션을 구축하기 위한 새로운 모델을 도입합니다.
    이제 React에서는 컴포넌트를 렌더링할 위치를 선택할 수 있어 클라이언트 측 번들 크기를 줄이고 기본 클라이언트 측 런타임 크기를 일정하게 유지할 수 있습니다.
  • 💻 서버 컴포넌트는 클라이언트 측 번들 크기를 줄여 초기 페이지 로딩 속도를 높이고 서버 인프라 활용도를 향상시킵니다.
    Next.js의 모든 컴포넌트는 기본적으로 서버 컴포넌트로 취급되며 "use client" 지시문을 사용하여 클라이언트 컴포넌트로 변경할 수 있습니다.
  • 💻 서버 컴포넌트를 사용하면 초기 페이지 로딩 속도가 빨라지고 클라이언트 측 자바스크립트 번들이 작아지며, 클라이언트 측 상호 작용이 클라이언트 컴포넌트를 통해 사용될 때만 추가 자바스크립트가 추가됩니다.
  • 🖥️ 클라이언트 컴포넌트를 사용하면 클라이언트 측 상호 작용을 추가할 수 있으며 서버에서 프리렌더링되고 Next.js에서 클라이언트에서 하이드레이션됩니다.
  • 🤝 기본적으로 서버 사이드 렌더링이 주기능인 Next.js이기 때문에, 클라이언트 컴포넌트에 대한 사용 사례를 충분히 확보할 때까지 서버 컴포넌트를 사용하는 것이 좋습니다.
  • 🧠 서버 컴포넌트를 사용하면 PHP 또는 Ruby on Rails와 유사한 느낌으로 React 애플리케이션을 작성할 수 있습니다.
반응형

댓글