본 글은 https://nextjs.org/docs/getting-started/react-essentials 를 기반으로 작성되었습니다.
목표 : next 13을 본격적으로 공부하기 전에, 메인이 되는 서버 컴포넌트가 정확히 무엇인지 이해하기
다룰 내용
기존에는 default가 client 베이스였고, 페이지에 따라 SSR과 CSR을 나눌 수 있었음.
이제는 페이지별로 나누는게 아닌 컴포넌트별로 나눌 수 있게 됨.
기본 렌더링 방식은 SSR이고 내부는 Client 컴포넌트로 구성 가능
-> next.js의 서버 우선 접근 방식과 일치
Next 13의 App directory에 있는 모든 컴포넌트는
Server component
(page 뿐만 아니라 special files 및 같은 파일 내 존재하는 컴포넌트 포함)
- special files : error.tsx, layout.tsx ...
- next 13부터는 app 내에 페이지 뿐만 아니라 컴포넌트도 함께 위치시킬 수 있음 (app/main/page.tsx 와 app/main/input.tsx는 같은 main 폴더 내에 있지만, routing은 page만 가능)
"use client"
을 파일 맨 앞에 명시해 클라이언트 컴포넌트로 지정 가능
명시된 파일에 사용된 모든 모듈들은 client 번들에 포함됨.
클라이언트에서 반드시 사용되어야하는 상황이 아니라면 서버 컴포넌트 권장
'use client';
// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
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 /> // ❌❌❌
</>
);
}
이렇게 클라이언트 컴포넌트 내부에 서버 컴포넌트를 바로 넣어두는 것은 지원하지 않는 패턴
앞서 언급했듯 이렇게 되면 서버컴포넌트를 클라이언트에서 한번 더 계산해 줘야하기 때문 (round trip)
해결 방법 : 클라이언트 컴포넌트에 서버 컴포넌트 Props
로 전달하기
주로 children
형식으로 넘겨주는 방식이 사용됨.
props 형태로 넘겨주는 이유는 클라이언트 컴포넌트 자체에서 들어오는 props가 무엇인지 알 필요가 없고 단지 이 props가 어디에 들어갈지 위치만 알고 있으면 되기 때문
'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}
</>
);
}
서버에서 클라이언트로 전달되는 props들은 serialization 과정을 거침.
따라서 Props로 전달 시, functions, Date와 같은 형식은 전달 불가
Serialization?
네트워크나 스토리지에 전달되기 위한 파일 형태로 변환하는 것.
JS에서는Json.stringify()
를 이용해 서버에 데이터를 보내는 방식을 의미한다. 따라서 deep copy가 불가능한 것들(unserializable)은 props로 보낼 수 없다.
서버에서만 돌아가야 하는 코드가 클라이언트 컴포넌트에 침투하게 될 수 있음.
이를 막기 위해 server-only
packages 사용
npm install server-only
설치 후, 서버에서만 사용되어야 하는 모듈에 import 해줌.
클라이언트 컴포넌트에서 해당 모듈 사용시, build-time error 발생
자매품
client-only
도 있음
서버에서만 사용되는 케이스
next_public
이 붙지 않은 환경 변수는 서버에서만 접근 가능하기 때문에, 서버에서 안전하게 key를 이용해서 데이터를 받아와야 하는 경우, 클라이언트에서 해당 모듈을 사용하면 안됨.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(); }
대부분의 라이브러리들은 client 전용으로 만들어졌지만, "use client"
가 표기되어 있지 않기 때문에, 서버 컴포넌트에서 호출 시 에러가 발생하게됨.
이를 막기 위해 외부 라이브러리의 컴포넌트를 별도의 컴포넌트로 다시 만들어서 사용해주어야 함.
"use client"
import { clientComponent } from "third-party-library"
export default clientComponent
Server는 상태(state)를 갖지 않기 때문에 context는 클라이언트에서만 지원됨.
따라서 Provider도 위의 라이브러리처럼 client로 감싸서 사용해야함.
서버 컴포넌트간 데이터 공유가 필요한 경우,
데이터 자체를 공유하는게 아닌 DB에 대한 엑세스를 공유 (global singleton)
동일한 접근자를 가지고 각각의 컴포넌트에서 각자 데이터 호출
어처피 중복된 fetch 호출에 대해 next가 중복을 제거해 처리하기 때문에, 반복 호출에 대해 걱정할 필요가 없음.