[next13] Server components

dana·2023년 6월 4일
17

Next.js

목록 보기
7/13
post-thumbnail

본 글은 https://nextjs.org/docs/getting-started/react-essentials 를 기반으로 작성되었습니다.

React Essentials

목표 : next 13을 본격적으로 공부하기 전에, 메인이 되는 서버 컴포넌트가 정확히 무엇인지 이해하기

다룰 내용

  • 서버 컴포넌트 VS 클라이언트 컴포넌트
  • 서버 컴포넌트를 언제 사용하면 좋을지
  • 추천하는 사용 패턴

서버 컴포넌트

기존에는 default가 client 베이스였고, 페이지에 따라 SSR과 CSR을 나눌 수 있었음.
이제는 페이지별로 나누는게 아닌 컴포넌트별로 나눌 수 있게 됨.

기본 렌더링 방식은 SSR이고 내부는 Client 컴포넌트로 구성 가능
-> next.js의 서버 우선 접근 방식과 일치

왜 서버 컴포넌트를 사용해야 하는가

  1. 개발자들이 서버 인프라를 더 잘 활용할 수 있게 해줌
    • 데이터를 가져오는 부분을 DB랑 더 가까운 서버로 옮김
    • JS 번들 크기에 영향을 미치던 대규모 종속성을 서버에 유지해 성능 개선
    • 서버 컴포넌트 사용시 PHP나 Ruby on Rails 처럼 사용하면서도, React의 장점(UI지향형 컴포넌트, 유연성 등)을 살릴 수 있음.
  2. 초기 페이지의 로딩이 빠르고, Client-side 번들 사이즈가 줄어듦
    • 기본 client-side runtime 캐싱 가능하고 크기가 예측 가능
    • 그 외에 서버 컴포넌트로 사용되는 코드들은 크기게 영향을 주지 않음 (서버에서 처리되기 때문)

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 번들에 포함됨.

언제 어떤 컴포넌트를 사용하는게 좋을까?

클라이언트에서 반드시 사용되어야하는 상황이 아니라면 서버 컴포넌트 권장

  • event handler 사용 (onClick, onChange ...)
  • useHook 사용시 (useState, useEffect ...)
  • 위의 hook을 사용한 custom hook 사용시
  • browser API 사용시
  • React class component 사용시

사용 패턴

1. 클라이언트 컴포넌트를 DOM 트리의 가장 끝단으로 만들어 사용하기

2. 클라이언트 컴포넌트와 서버 컴포넌트 병합하기

React에서 렌더링하는 방식

  • 클라이언트에 결과를 전송하기 전에 모든 서버 컴포넌트 렌더링
    - 이 때, 클라이언트 컴포넌트에 포함된 서버 컴포넌트도 포함
    • 클라이언트 컴포넌트 자체는 스킵됨
  • 클라이언트에서 렌더링하면서, 서버에서 만들어진 결과물을 끼워 넣음

Next.js에서 렌더링하는 방식

  • 일단 서버에서 서버 컴포넌트와 클라이언트 컴포넌트 모두 HTML로 변환(pre-render)

3. 클라이언트 컴포넌트 안에 서버 컴포넌트 넣기

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

4. 서버 컴포넌트에서 클라이언트 컴포넌트로 props 전달하기 (serialization)

서버에서 클라이언트로 전달되는 props들은 serialization 과정을 거침.
따라서 Props로 전달 시, functions, Date와 같은 형식은 전달 불가

Serialization?
네트워크나 스토리지에 전달되기 위한 파일 형태로 변환하는 것.
JS에서는 Json.stringify()를 이용해 서버에 데이터를 보내는 방식을 의미한다. 따라서 deep copy가 불가능한 것들(unserializable)은 props로 보낼 수 없다.

https://developer.mozilla.org/en-US/docs/Glossary/Deep_copy

5. 클라이언트 컴포넌트 밖에서 Server-only code 유지하기

서버에서만 돌아가야 하는 코드가 클라이언트 컴포넌트에 침투하게 될 수 있음.
이를 막기 위해 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();
}

6. 외부 라이브러리 사용

대부분의 라이브러리들은 client 전용으로 만들어졌지만, "use client"가 표기되어 있지 않기 때문에, 서버 컴포넌트에서 호출 시 에러가 발생하게됨.

이를 막기 위해 외부 라이브러리의 컴포넌트를 별도의 컴포넌트로 다시 만들어서 사용해주어야 함.

"use client"

import { clientComponent } from "third-party-library"

export default clientComponent

Context

Server는 상태(state)를 갖지 않기 때문에 context는 클라이언트에서만 지원됨.
따라서 Provider도 위의 라이브러리처럼 client로 감싸서 사용해야함.

서버 컴포넌트간 데이터 공유가 필요한 경우,
데이터 자체를 공유하는게 아닌 DB에 대한 엑세스를 공유 (global singleton)
동일한 접근자를 가지고 각각의 컴포넌트에서 각자 데이터 호출

어처피 중복된 fetch 호출에 대해 next가 중복을 제거해 처리하기 때문에, 반복 호출에 대해 걱정할 필요가 없음.

profile
PRE-FE에서 PRO-FE로🚀🪐!

0개의 댓글