Next.js 에서, 너는 리액트 서버 컴포넌트와 클라이언트 컴포넌트를 사용할 수 있다. app 경로에서 기본적으로 모든 컴포넌트들은 서버 컴포넌트이다. 이 게시글에서, 우리는 리액트 서버 컴포넌트와 클라이언트 컴포넌트를 깊게 알아볼 예정이다.
첫번째로, 각각의 개념에 대해서 기본개념을 다뤄보자.
Serialization(직렬화)는 너가 만약 클라이언트와 서버 컴포넌트를 사용한다면 알고 있어야할 중요한 주제이다. 너는 이게 왜 중요한지 이 게시글의 뒷부분에서 알게 될 것이다.
Serialization은 객체가 파일 시스템, DB 또는 메모리에 저장 될수 있도록 하기 위해서 객체를 바이트 스트림으로 변환하는 과정이다.
너의 메모리나, DB, 파일시스템에 어떠한 것을 저장하기 위해서는, 너는 저장하고 싶은 것들을 바이트 스트림으로 유지해놓아야 한다.
그렇지 않다면, 너의 컴퓨터는 무슨일이 일어나고 있는것인지 이해할 수 없기때문에 제대로 동작하지 않는다.
리액트 서버 컴포넌트들은 서버에서 렌더되고 요청되는 컴포넌트들이다.
너가 만약에 클라이언트 쪽의 번들에서 서버컴포넌트들을 찾는다면, 그것들은 존재하지 않을것이다. 예를 들어서
위의 예는 2개 블로그 포스트 글들이 있는 간단한 블로그 예시이다.
태그 컴포넌트라고 불리는 핑크색 컴포넌트는 데모의 목적으로 컴포넌트가 클라이언트 컴포넌트인지, 서버 컴포넌트인지 알리기 위해 사용된다.
다른 것들은 Page, Card, List, 그리고 Date 컴포넌트들이다.
너가 만약에 Next.js 13의 라우팅을 다시 알아야한다면, 내가 쓴 게시물을 살펴봐라. 게시물 링크
기본적으로, Next.js 13 에서 컴포넌트들은 서버사이드 컴포넌트들이다.
서버 컴포넌트들의 중요한 이점들중 가장 큰 이점은, 서버컴포넌트들은 클라이언트 사이드 번들을 포함하지 않는다. 이것은 적은 양의 클라이언트 사이드 번들을 우리가 전달할 수 있도록 해서, 사용자가 적은 자바스크립트 코드들을 다운로드 하도록 한다. 이것은 너가 핸드폰 데이터 요금제를 사용하거나, 인터넷이 불안정한 도시에서 핸드폰을 사용할때 큰 도움을 준다.
너는 웹팩 번들을 볼 수 있다.
"use client" 로 표시되거나 Date 같은 웹팩 안에 포함되어있는 클라이언트 컴포넌트 안에 있는 컴포넌트들만 웹팩 번들에 포함된다.
위의 경우에서 Page, Card, List 컴포넌트들은 서버컴포넌트들이고 Card, Date들은 클라이언트 컴포넌트들이다.
일반적인 어플리케이션에는 서버와, 브라우저라고 불리는 클라이언트가 있다.
브라우저는 데이터를 요청하고, 해당 페이지에서 자바스크립트를 렌더한다.
그리고 너의 API 데이터를 서버에 요청한다.
이것은 클라이언트가 API 데이터를 요청하는 경우를 제외하고는 작동한다.
데이터가 받아지면, 그 데이터를 페이지에 렌더한다. 이런 과정들은 보통 Hydration으로 알려져있다.
그러나 round trip (?)을 저장하지 않는 이유는 무엇일까?
만약 서버가 브라우저가 필요한 데이터들을 렌더링하고, 데이터를 요청할 수 있는 기능이 있다면 어떨까?
이런 점들이 바로 리액트 서버 컴포넌트들이 탄생한 이유이다.
클라이언트 사이드 컴포넌트들은 클라이언트(즉, 너의 브라우저)에서 렌더되고 fetch되는 컴포넌트들이다. 클라이언트 컴포넌트들은 브라우저의 자바스크립트 번들을 포함하고 있는 일반적인 리액트 컴포넌트들이다.
리액트 서버컴포넌트들은 서버에서 렌더되고 fetch되는 반면에, 클라이언트 컴포넌트들은 클라이언트쪽에서 렌더되고 fetch된다.
만약 너가 lifecycle hooks같은 상호작용성을 포함하는 컴포넌트라면 그것을 클라이언트 컴포넌트로 만들어라. 만약 그렇지 않다면 서버컴포넌트로 작성해라.
너가 컴포넌트들을 선언하는 곳이 중요하기 때문에 그렇지 않다.
예를들어서, 너가 만약 클라이언트 컴포넌트 안에서 클라이언트 컴포넌트라고 선언한 Calendar 이라는 컴포넌트가 있다면, 잘 동작할 것이다.
만약 너가 "use client" 선언을 지운다면, 이 컴포넌트는 서버 컴포넌트가 되고 에러가 발생 할 것이다.
만약에 그들이 서버에서 렌더될수 있으면 서버에서 렌더되어야 하기 때문에,클라이언트 컴포넌트들을 가능한 leave 노드들로 유지해라.
사용자들이 더 적은 자바스크립트를 다운 받을수 있도록 하는 양이 적어진 자바스크립트 번들(클라이언트 번들 쪽에 포함되지 않는에)같은 많은 이점들을 얻을수 있기 때문이다.
서버 컴포넌트들은 서버사이드 렌더링과 같지 않지만 상호 보완적이다. 둘다 'server'라는 용어를 사용해서 혼동 할 수 있다.
이 둘을 비교해보기전에 서버사이드 렌더링이 무엇인지 살펴보자.
웹사이트들은 자바스크립트, HTML, CSS에 의존하고 서버로부터 데이터를 요청한다.
웹페이지의 첫번째 로드는 필수적이므로, SSR은 해당 페이지의 HTML 컨텐츠를 미리 빌드한다.
이것은 SEO에 효과적이고, 사용자가 자바스크립트 코드를 다운받는 동안 해당 페이지의 컨텐츠를 볼수 있다. 서버의 결과물은 HTML이다.
SSR은 클라이언트 컴포넌트의 HTML을 보여줌으로써 사용자가 페이지를 로드할때 초기 부팅시간을 줄여줄수 있도록 한다.
그러나, HTML이 로드 됐을때 아직 다운로드를 해야한다면, 컴포넌트들을 파싱해야한다.
우리는 SSR을 이해했으니 둘의 차이점을 알아보자
서버 컴포넌트들은 HTML 자체를 반환하기보다, 렌더된 UI에 대한 설명을 반환한다.
이러한 방식은 리액트가 상태값을 잃지 않고 존재하는 클라이언트 컴포넌트들과 데이터를 영리하게 병합하도록 한다.
너는 아래에서 중간 state를 볼 수 있다.
브라우저의 stream 은 더이상 JSON이 아니지만, JSON과 비슷한 이미지 처럼 보인다.이것은 리액트 서버 컴포넌트들이 페이지에 컴포넌트들을 렌더하기 위해 다른 stream type을 사용하기 때문이다.
우리가 서버 컴포넌트 에서 클라이언트 컴포넌트로 props를 전달할때, 서버 컴포넌트들은 serializable (직렬화)가 되어야 한다.
props로써 () => {} 같은 화살표 함수를 전달할수 없다. 대신에, 우리는 JSON으로 직렬화 될수 있는 props들을 전달할 수 있다.
항상 모든 prop들을 직렬화하는것은 번거로울 것이다. 너는
<p>Hi</p>
같이 JSON으로 직렬화 될수 있는 요소들은 전달할 수 있다.
만약에 너가 너의 코드들이 클라이언트 컴포넌트가 아니라 서버컴포넌트에서만 명백히 작동하도록 하게 하고 싶다면, "sever-only" 라는 새로운 패키지를 설치할 수 있다.
기본적으로 컴포넌트들은 서버 컴포넌트들이지만, 너가 이것들을 명백하게 하고싶고 갑작스럽게 import하고싶지 않다면, 이 패키지를 사용하면 된다.
어떻게 컴포넌트들에서 전역 state들을 공유할 수 있을까?
일반적으로, 너는 _app.js 컴포넌트 안에서 context 같은 전역 상태값들을 저장했을 것이다.
하지만 Next.js 13에서는 _app.js는 존재하지 않고 _app.js는 RootLayout으로 대체되었다.
예를 들어서
그러나 만약에 RootLayout안에서 context를 만들었다면, 그것들은 동작하지 않을것이다.
기본적으로 RootLayout은 서버컴포넌트이고, 서버 로직이 그 안에 있기 때문에 클라이언트 context는 동작하지 않는다.
너는 위의 코드를 이렇게 고칠 수 있다.
너는 "use client"를 사용해서 오직 클라이언트 코드만 들어가는 컴포넌트를 만들고 그 안에서 너의 context를 만들 수 있다. 예를 들어
다시, 써드 파티 providers들은 어떤가? 이것들을 사용하려면, "use client" 선언이 필요하다. 하지만, GlobalProviders라고 불리는, 너의 providers를 전부 렌더하는 일반 파일을 만들수 있다. 그리고 그것들 RootLayout에서 import 해올 수 있다.
너가 만약에 클라이언트 컴포넌트와 서버 컴포넌트 두 컴포넌트에서 둘다 작동하는 컴포넌트를 만들고 싶다면 어떻게 해야할까?
DRY(Do not repeat yourself) 원리를 따르기 위해서 두 컴포넌트들 사이에서 로직이 공유되도록 해야한다.
너는 상호작용성 없이 흔한 컴포넌트들의 로직들을 추상화 할 수 있고, 그것들을 사용하고 싶은 곳 어디에서든지 선언할 수 있다.
예를 들어서, 너가 만약 "client-only" 컴포넌트를 선언 했다면, 그것은 클라이언트 컴포넌트딜 것이다. 반면에 너가 "sever-only" 컴포넌트를 똑같이 선언했다면, 그것은 서버 컴포넌트이고 선언할 필요 없을 것이다.
반면에, 서버컴포넌트에서 너는 서버컴포넌트 안에 이벤트핸들러가 없는지 확인해야한다. 만약에 이벤트 핸들러가 서버컴포넌트 안에 있다면 작동하지 않을 것이다.
여기 너가 공유 컴포넌트를 만드는 방법이 있다.
Date는 공유 컴포넌트이다. 우리는 이 컴포넌트를 서버 컴포넌트와 클라이언트 컴포넌트 두 곳에서 다 사용하고 싶다. 이렇게 할 수 있다.
어떻게 Card 컴포넌트가 client only 컴포넌트인지, Date가 여기서 어떻게 사용되는지 알아야한다. 이것은 client only 컴포넌트 일 것이다. 너가 만약 클라이언트 번들을 살펴본다면, Date는 클라이언트쪽 번들에 포함 될 것이다.
'use client';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import Date from './date';
import { Tag } from './Tags';
export default function Card({
id,
date,
title,
link,
formatType,
abstract,
imgUrl = '/card-bg.jpg',
}) {
const [imgSrc, setImgSrc] = useState('/card-bg.jpg');
useEffect(() => {
setImgSrc(imgUrl);
}, []);
const handleError = () => {
setImgSrc('/card-bg.jpg');
};
return (
<div
className={`w-full border-gray-700 border-solid border-spacing-4 border-2 m-2`}
>
<Link href={link}>
<Tag>Client Component: Card</Tag>
<li
className="flex flex-col mb-4 px-6 dark:bg-gray-900 bg-white text-black dark:text-white hover:cursor-pointer pb-2 overflow-hidden hover:text-gray-900 border border-solid mr-2 dark:border-gray-800 border-gray-100 glass-list"
key={id}
>
<>
<Image
src={imgSrc}
alt={`image for ${title}`}
width="400"
height="100"
onError={handleError}
/>
<h3 className="font-averta-bold outline-none mb-0"> {title}</h3>
<p className="ellipsis">{abstract}</p>
{date && (
<span className={'text-gray-951 text-xs flex flex-col'}>
<Tag>React Server component: Date</Tag>
<Date dateString={date} formatType={formatType} />
</span>
)}
</>
</li>
</Link>
</div>
);
}
하지만 만약 너가 Page 서버 컴포넌트 안에서 Date를 사용한다면, 이것은 서버에서 렌더되면서 Date를 "server-only" 컴포넌트로 만들 것이다. 페이지에서 Date는 클라이언트 번들에 포함 되지 않는다.