Next.js 13 Rendering : Server and Client Components

버건디·2023년 1월 17일
3

Next.js

목록 보기
11/52

서버 컴포넌트들과 클라이언트 컴포넌트들

서버 컴포넌트들과 클라이언트 컴포넌트들은 개발자들이 전통적인 서버렌더링의 향상된 퍼포먼스와 클라이언트 앱의 부유한 상호작용성을 결합하는 서버와 클라이언트를 엮는 어플리케이션을 빌드하도록 한다.
이 페이지는 서버, 클라이언트 컴포넌트들의 차이점과 그들을 Next.js 어플리케이션에서 어떻게 사용해야하는지 알려준다.

서버 콤포넌트들

앱 경로안에 있는 모든 컴포넌트들은 기본적으로 특별한 파일들이나 결합된 컴포넌트들 포함해서 리액트 서버 컴포넌트들이다. 이것은 너가 즉시 좋은 성능과 추가적인 동작 없이 서버컴포넌트들을 자동으로 채택하도록 허락한다.

왜 서버 컴포넌트들인가?

서버 컴포넌트들은 개발자들이 더 나은 서버 구축을 하도록 허락한다. 예를들어서, 전에 클라이언트쪽에서 자바스크립트 번들사이즈가 영향을 끼쳤던 큰 의존성들은 서버에 전적으로 유지하도록 대체하면서, 더 나은 성능을 야기한다.
Next.js에서 라우트가 로드됐을때, 초기 HTML은 서버에서 렌더된다. 이 HTML은 비동기적으로 Next.js와 리액트 클라이언트 환경을 비동기적으로 로딩하며 상호작용성을 추가하고 어플리케이션을 클라이언트에게 인계하도록 허락하며 브라우저 상에서 더 점진적으로 강화된다.
서버컴포넌트들과 함께, 초기 페이지 로딩은 더 빠르고 클라이언트 쪽의 자바스크립트 번들 사이즈는 감소된다. 클라이언트 실행환경에서는 캐싱 가능하고 예측가능한 사이즈가 기본이고, 너의 어플리케이션이 커질수록 더 커지지 않는다. 추가적은 자바스크립트는 오직 클라이언트 컴포넌트들을 통해 너의 어플리케이션이 사용되는 클라이언트 상호작용성에서 추가 된다.

클라이언트 컴포넌트들

클라이언트 컴포넌트들은 너의 어플리케이션에 클라이언트 상호작용성을 추가한다. Next.js에서 클라이언트 컴포넌트들은 클라이언트쪽에서 hydrated되고 서버에서 미리 렌더된다. 너는 Next.js 12버전에서 클라이언트 컴포넌트들이 어떻게 동작했는지 생각할 수 있다.

약속

파일의 맨 위에 "use client"를 선언함으로써 클라이언트 컴포넌트가 될 수 있다.
이 컴포넌트들과 파일들은 너의 어플리케이션 어디에서도 위치할 수 있다. 그들은 구체적으로 app/경로 안에 없어도 된다.
예를 들어서, 너는 components/ 경로에 원할수도 있다. 그러나, app/경로는 이전에 pages/경로에서 불가능 했던 페이지들과 컴포넌트들을 동시에 위치시킬수 있도록 한다.


너는 useState나 useEffect같은 훅을 사용할때 컴포넌트에 'use client'라고 선언할 필요가 있다. 이것은 다른 클라이언트 컴포넌트들로부터 임포트 되지 않을때 자동으로 서버컴포넌트들로서 렌더링 되도록 하기 위한 선언 없는 클라이언트 훅들에 의존하지 않는 컴포넌트들을 남겨두는 가장 좋은 방법이다. 이것은 클라이언트 쪽의 자바스크립트 양을 가장 적게 보장하도록 돕는다.


언제 서버 컴포넌트와 클라이언트 컴포넌트를 사용해야 하는가?

서버컴포넌트와 클라이언트 컴포넌트 사이에서 결정을 간단히 하기위해, 너가 클라이언트 컴포넌트를 사용해야하는 경우가 있을때까지 서버컴포넌트를 사용하도록 추천한다.
밑의 표는 서버컴포넌트와 클라이언트 컴포넌트의 사용 경우들을 정리해놓은 것이다.


클라이언트 컴포넌트에서 서버컴포넌트 importing 하기

서버컴포넌트와 클라이언트 컴포넌트들은 똑같은 컴포넌트 tree에서 동시 접근이 가능하다. 뒤에서 리액트가 두가지의 환경을 결합시켜준다.
그러나, 리액트에서 클라이언트 컴포넌트 안에 서버컴포넌트를 임포팅하는것은 제한이 있다. 왜냐하면 서버컴포넌트들은 오직 서버코드민 가질수 있을수도 있기 때문이다. (예를들어 데이터베이스나, 파일 시스템 유틸리티)
예를 들어, 클라이언트 컴포넌트에서 서버컴포넌트를 임포팅하는것은 작동하지 않는다.
대신에, 클라이언트 컴포넌트에서 서버컴포넌트를 자식이나 props로 받으면 가능하다. 너는 이것을 다른 서버 컴포넌트에서 두개의 컴포넌트를 감쌈으로서 가능하게 한다.
다른 서버컴포넌트에서 => 클라이언트 컴포넌트를 불러오고 그 클라이언트 컴포넌트는 자식props로 서버컴포넌트를 받는다.
이 패턴으로, 리액트는 클라이언트에세 결과를 보내기전에 서버에서 서버컴포넌트가 렌더링이 필요하다는 것을 알것이다. 클라이언트 컴포넌트 관점에서, 자식은 이미 렌더되어있다.
이 패턴은 이미 layouts과 pages에서 쓰였으므로 너는 다른 wrapper 컴포넌트를 만들필요가 없다.


서버컴포넌트에서 클라이언트 컴포넌트로 props 전달(직렬화)

서버컴포넌트에서 클라이언트 컴포넌트로 props를 전달하는것은 직렬화가 필요하다. 이 의미는, 함수들이나 dates, 기타등등이 클라이언트 컴포넌트들에게 직접적으로 전달될수 없는것을 의미한다.
네트워크 경계는 어디있나?
앱 경로에서, 네트워크 경계는 서버컴포넌트와 클라이언트 컴포넌트 사이에 있다. 이것은 페이지 컴포넌트들과 getStaticProps/getServerSideprops 사이에 있는 경로가 있는 페이지 경로와는 다르다. 서버컴포넌트 안에서 fetch 된 데이터는 클라이언트 컴포넌트에게 전달되지 않는다면 네트워크 경계를 지나지 않기때문에 직렬화가 필요없다.


클라이언트 컴포넌트에서 Sever-only 코드 유지

자바스크립트 모듈들은 서버와 클라이언트 컴포넌트들 사이에서 공유되기 때문에, 이것은 서버에서만 실행되도록 의도한 코드가 클라이언트 안으로 들어가게 가능하도록 한다.
예를 들어서, 이 데이터 fetching 함수를 보자.
처음에는, 이것은 서버와 클라이언트 둘다 작동하는것처럼 보인다. 하지만 환경변수는 NEXT_PUBLIC라는 접두사와 정의되어있지 않기때문에, 이것은 서버에서만 접근가능한 private한 변수이다. Next.js는 보안 정보의 유출을 방지하기 위해서 private 환경변수를 빈 문자열로 클라이언트 코드에서 대체한다.
결과적으로, getData() 함수가 클라이언트에서 실행되고 임포트되더라도, 기대대로 작동하지는 않는다. 그리고 클라이언트쪽에서 변수를 공개적으로 만들게되면 민감한 정보가 유출되게 된다.
그러므로, 이 함수는 서버에서만 실행되게하는 의도를 가지도록 만들어졌다.
서버코드의 의도하지않은 클라이언트쪽에서 사용하는것을 방지하기 위해서, 우리는 개발자들에게 만약에 그들이 클라이언트 컴포넌트 안에서 이러한 모듈들중에 하나를 갑자기 임포트를 만약에 한다면 sever-only 패키지를 제공한다.
server-only 를 사용하기 위해서는 설치해야한다.


그리고 server-only 코드를 포함하고 있는 어떠한 모듈에서 이 패키지를 불러오면 된다.
지금부터, getData()를 임포트하는 어떠한 클라이언트 컴포넌트들이라도 서버에서만 이 모듈을 사용가능하다는 에러메세지를 가진 에러를 빌드시에 받게 된다.
예를들어서 window 객체에 접근하는 코드를 가진 client-only 코드를 포함하는 모듈들 또한 client-only 패키지를 이용함으로써 사용할 수 있다.


나뭇잎들로(?) 클라이언트 컴포넌트들 옮기기

너의 어플리케이션의 성능을 향상시키기 위헤ㅐ서, 우리는 가능한 너의 컴포넌트들의 leaves로 클라이언트 컴포넌트들을 옮기길 추천한다.
예를 들어서, state를 사용하는 상호작용적인 검색바와 정적인 요소들을 가지고 있는 레이아웃들을 너가 가지고 있을 수 있다.
클라이언트 컴포넌트의 전체 레이아웃을 만드는것 대신에, 클라이언트 컴포넌트에게 상호작용적인 로직들을 옮기고, 서버컴포넌트들로써 너의 레이아웃을 유지할 수있다. 이것은 너가 클라이언트에게 레이아웃의 모든 컴포넌트의 자바스크립트를 보낼 필요가 없다는것을 의미한다.


써드파티 패키지들

use-client 선언은 서버컴포넌트의 한부분으로 소개됐던 새로운 리액트 기능이다. 서버컴포넌트들은 새롭게 등장했기때문에, 생태계에서 써드파티 패키지들은 useState나 useEffect, createContext 같이 client-only 기능을하는 컴포넌트들에 이제 막 추가되기 시작했다.
오늘날, client-only 기능들을 사용하는 npm packages들로부터 많은 컴포넌트들은 아직 선언을 가지지 못했다. 이러한 써드파티 컴포넌트들은 "use client"라는 선언을 가지기 때문에 너만의 클라이언트 컴포넌트들과 함께 동작할것이다. 하지만 그들은 서버 컴포넌트들과 동작하진 못한다.


만약에 너가 AcmeCarousel 이라는 클라이언트 컴포넌트를 사용한다면 이렇게 동작할 것이다.

하지만 서버컴포넌트와 직접적으로 같이 사용하려고 하면 에러가 등장한다.
이것은 Next.js는 AcmeCarousel 컴포넌트가 client-only기능을 사용하는지 모르기 때문이다.

이걸 고치기 위해, 너는 너의 클라이언트 컴포넌트들에서 client-only 기능에 의존하는 써드파티 컴포넌트들을 감쌀 수 있다.
너는 이제 서버컴포넌트와 함께 있는 Carousel 컴포넌트를 사용할 수 있다.

우리는 너가 클라이언트 컴포넌트들과 같이 써드파티 컴포넌트들을 사용할 가능성이 있다는 것을 알기때문에 대부분의 써드파티 컴포넌트들을 감싸는게 필요하도록 기대하지 않는다. 하지만, provider 컴포넌트들은 예외이다. 그들은 어플리케이션의 루트에서 필요해지고, context와 리액트 state에 의존하기 때문이다.


데이터 요청

클라이언트 컴포넌트에서 데이터 요청이 가능하지만, 우리는 너가 클라이언트쪽에서 특별하게 데이터를 요청해야할 이유가 없다면 서버컴포넌트에서 데이터요청을 하는것을 추천한다. 서버 컴포넌트에서 데이터 요청을 하는것은 더나은 성능과 사용자 환경을 제공한다.


Context

대부분의 리액트 어플리케이션은 createContext를 직접적으로 통하거나, 써드파티라이브러리에서 임포트해온 Provider 컴포넌트들을 간접적으로 통해 컴포넌트들 사이에서 데이터를 공유하기 위한 context에 의존한다.
Next.js 13 에서는, 클라이언트 컴포넌트들과 함께 context기능은 지원된다. 하지만 서버컴포넌트들과 직접적으로 만들어지거나 사용될수는 없다. 서버컴포넌트들은 리액트 state가 없기때문이며, 그리고 context는 일부 리액트 state가 업데이트 된 후에 구성요소를 다시 렌더링하는데 사용되기 때문이다.
우리는 서버컴포넌트들사이에서 데이터를 공유하는 대안을 논의하겠지만, 일단 클라이언트 컴포넌트에서 context를 사용하는 경우를 보자.


클라이언트 컴포넌트 내에서 context 사용


하지만, 컨텍스트 providers는 현재 테마 같이 전역적인 상태들을 공유하기 위해 어플리케이션 root 근처에서 전형적으로 렌더된다. 왜냐하면 서버컴포넌트에서 컨텍스트는 지원되지 않기때문인데, 어플리케이션의 루트에서 컨텍스트를 만드는것은 에러를 일으킬수 있다.


이것을 고치기 위해, 클라이언트 컴포넌트 안에서 너의 컨텍스트를 만들고 provider 컴포넌트를 렌더해라.

너의 서버 컴포넌트는 클라이언트 컴포넌트로서 표시되었기 때문에 너의 provider를 직접적으로 렌더 할 수 있다.
root 에서 렌더된 provider와 함께, 모든 다른 너의 어플리케이션을 통하는 클라이언트 컴포넌트들은 이 context를 사용할 수 있다.
알아둘 점: 너는 tree 안에 provider들을 최대한 깊숙히 렌더해야한다. ThemeProvider 가 html 전체를 감싸는게 아니라 children 만 감싸는걸 알아야한다. 이것은 서버컴포넌트들의 정적인 부분들을 Next.js가 더 쉽게 선택할수 있도록 만든다.


서버 컴포넌트들안에서 써드 파티 context providers 렌더링

가끔 써드 파티 Npm 패키지들은 너의 어플리케이션 root 가까이에서 렌더링 되어야할 필요가 있는 Providers 들을 포함한다. 만약 이 providers가 "use client"선언을 포함한다면, 그들은 서버컴포넌트 안에서 직접적으로 렌더링 될것이다. 그러나, 서버컴포넌트들은 새롭기 때문에 많은 써드파티 Providers들이 이 선언을 추가하지 못했다.
너가 만약 "use client"를 가지고 있지 않은 써드 파티 provider를 렌더링하려 한다면 에러를 일으킬 것이다.

이것을 해결하기 위해, 너만의 클라이언트 컴포넌트안에서 써드파티 Providers들을 감싸라.

이제 너는 너의 Root에서 Providers를 임포트해오고 렌더할수있다.
root에서 렌더링되는 providers와 함께, 이러한 라이브러리들에서 온 모든 컴포넌트들과 hooks들은 너만의 클라이언트 컴포넌트들에서 작동할 것이다.
써드파티 라이브러리들이 그들의 클라이언트 코드에서 "use client"를 추가한다면, 너는 감싼 클라이언트 컴포넌트들을 제거 할 수 있다.


서버컴포넌트들 사이에서 데이터 공유

서버 컴포넌트들은 상호작용적이지 않고 react state로부터 값들을 읽어오지 않기때문에, 너는 데이터를 공유하기 위해서 context 의 모든 힘이 필요하지않다. 너가 만약 다양한 서버컴포넌트에서 접근 해야할 데이터를 가지고 있다면 너는 모듈 scope와 함께 전역적인 싱글톤들 같은 순수한 자바스크립트 패턴들을 사용할 수 있다.
예를 들어서, 모듈은 다양한 컴포넌트들을 거쳐서 데이터베이스 연결을 공유할수 있다 .

위의 예에서, 페이지와 layout 둘다 데이터베이스 쿼리를 만들어야할 필요가 있었다. 각각의 컴포넌트들은 database 모듈을 임포팅함으로써 데이터베이스에 대한 접근을 공유한다.


서버컴포넌트들 사이에서 fetch 요청 공유

데이터를 요청할때, 너는 몇몇 자식컴포넌트들과 layout 이나 page 사이에서 fetch 한 결과물을 공유하고 싶을 수 있다. 이것은 컴포넌트들 사이에서는 불필요한 결합이고, 컴포넌트들 사이에서 props들이 앞뒤로 전달 될 수 있다.
대신에, 우리는 데이터요청을 데이터를 소비하는 컴포넌트들과 나란히 위치시키는걸 추천한다. 데이터 요청은 서버컴포넌트들안에서 자동으로 중복 제거가 되기 때문에, 각각의 라우트 segment는 요청이 중복되는것에 대한 걱정없이 정확한 데이터를 요청할 수 있다. Next.js 는 캐시된 데이터에서 같은 값을 읽어올 것이다.

profile
https://brgndy.me/ 로 옮기는 중입니다 :)

0개의 댓글