[10분 테코톡] 서버 컴포넌트

흑우·2023년 11월 29일

10분 테코톡 - 5기

목록 보기
5/16

무엇이 서버 컴포넌트인가?

  • 간단합니다! 컴포넌트가 서버에서 렌더링되면 서버 컴포넌트입니다.
  • 서버에서 렌더링된 컴포넌트는 클라이언트로 전달되어서 보이게됩니다.

서버 컴포넌트의 특징

  • 리렌더링 되지 않는다.
  • 서버 리소스에 접근 가능하다. (파일 시스템, 데이터베이스 등)
  • 자바스크립트 번들에 포함되지 않는다. => 제로 번들 사이즈
  • 자동 코드 스플릿팅 -> 임포트 되는 컴포넌트 자동으로 splitting point로 간주 -> lazy 명시 필요 X
  • 유저 인터랙티비티 제공 불가

서버 사이드 렌더링과의 차이점

  • 리액트 서버 컴포넌트와 서버 사이드 렌더링은 같은 개념이 아닙니다.

  • 초기 HTML을 생성 => 서버 사이드 렌더링에 여전히 의존

  • 컴포넌트를 자바스크립트 번들에 포함되지 않게, 서버에서 실행시키고 싶다 -> 서버 컴포넌트

  • 서버 사이드 렌더링의 모든 컴포넌트 코드는 자바스크립트 번들에 포함됩니다.

  • 하지만 서버 컴포넌트의 코드는 자바스크립트 번들에 포함되지 않습니다.

  • 서버 사이드 렌더링에서 백엔드 리소스에 접근하려면 최상위 페이지에서 getServerProps()로 접근해야 합니다.

  • 서버 컴포넌트를 사용하게 되면 페이지 레벨에 상관없이 백엔드 리소스 접근 가능

  • 서버 사이드 렌더링의 경우 상태를 저장하고 refetch를 시켜주면 html을 다시 받아오기 때문에 상태의 보존이 불가능합니다.

  • 서버 컴포넌트의 경우 refetch를 하고 RSC Payload라는 특별한 방법으로 받아오기 때문에 상태의 보존이 가능합니다.

  • 서버 컴포넌트는 서버 사이드 렌더링의 대체재가 아닙니다.

  • 서버 사이드 렌더링 => 초기 렌더링 + 서버 컴포넌트 => 제로 렌더링

    • 더욱 빠른 페이지 제공 가능
    • 대체 관계가 아닌 보완 관계

리액트 서버 컴포넌트를 어떻게 사용하는가?

  • 아쉽게도 베이식한 리액트만으로는 서버 컴포넌트를 사용하기 쉽지 않다.
    • Next.js 13의 App Router를 사용하는 것이 가장 추천되는 방식
  • Next.js 13의 App Router에서는 모든 컴포넌트가 기본적으로 서버 컴포넌트
  • 클라이언트 컴포넌트를 사용하고 싶다면? 'use client' 지시문 사용
// 클라이언트 컴포넌트
'use client'

const App = () => {
	const [state, setState] = useState(0);
  
  return <></>
}

어떤 기준으로 서버/클라이언트 컴포넌트를 나누어야 하는가?

  • 모든 컴포넌트를 서버 컴포넌트로 만들 수 있다면 좋습니다. 하지만 쉽지 않죠.

  • 상태, 이펙트, 또는 브라우저 API를 사용하거나 유저와 상호작용이 필요하다면 클라이언트 컴포넌트로 구현해야 합니다.

  • 서버 컴포넌트와 클라이언트 컴포넌트를 동시에 사용

Boundaries (경계)

  • 해당 컴포넌트 트리의 모든 컴포넌트가 서버 컴포넌트라면 문제 없이 잘 동작합니다.
  • 하지만 특정 컴포넌트를 클라이언트 컴포넌트로 만들어서 리렌더링이 일어난다면?
    • 자식 컴포넌트들은 서버 컴포넌트 이기 때문에 리렌더링이 발생하지 않는다.
    • 리액트 개발자들은 클라이언트 컴포넌트가 임포트하는 컴포넌트는 반드시 클라이언트 컴포넌트여야 한다 라는 규칙을 추가했습니다.

  • 이를 통해서 Client Boundary가 생성됩니다. (클라이언트 컴포넌트의 자식 컴포넌트는 모두 클라이언트 컴포넌트화)
  • 이는 서버 컴포넌트를 사용해야 하는 입장에서는 제약 조건인데요.
  • 이런 Client Boundary 제약조건 문제를 해결하기 위한 방법은 없을까요?
    • 애플리케이션을 재구성 -> 컴포넌트의 소유자 변경
  • 다크 모드 / 라이트 모드를 전환하기 위한 코드입니다.
'use client'

type ColorTheme = 'light' | 'dark';

const App = () => {
	const [colorTheme, setColorTheme] = useState<ColorTheme>('light');
  	
  	const styleVariables: CSSProperties = 
      colorTheme === 'light'
  		? { backgroundColor: "#fff" }
  		: { backgroundColor: "#3c3c32"};
  
  	return (
    	<div>
        	<Header />
        	<Main />
        </div>
    )
}
  • Header 컴포넌트와 Main 컴포넌트는 Client Boundary에 의해서 클라이언트 컴포넌트화 될탠데요.
  • State를 사용하는 부분만 따로 컴포넌트 분리(파일을 분리) -> 'use client'
const ColorProvider = (props: ColorProviderProps) => {
	const { children } = props;
  
  		const [colorTheme, setColorTheme] = useState<ColorTheme>('light');
  	
  	const styleVariables: CSSProperties = 
      colorTheme === 'light'
  		? { backgroundColor: "#fff" }
  		: { backgroundColor: "#3c3c32"};
  
  	return <div style={styleVariables}>{children}</div>
}

const App = () => {
	return (
    	<ColorProvider>
        	<Header />
        	<Main />
        </ColorProvider>
    )
}
  • 기존 컴포넌트에서는 클라이언트 컴포넌트의 기능을 사용하지 않음

  • 자식 컴포넌트들도 클라이언트 컴포넌트화 되지 않음

  • 컴포넌트 트리 구조 상 파일을 분리했지만 여전히 상위에 위치하는데 클라이언트 컴포넌트화 되지 않을까요?

    • Client Boundary에선 부모/자식이 중요한게 아니다.
    • import하는 컴포넌트에 집중
    • Header, Main의 렌더링 -> App 컴포넌트가 결정
  • 이런 방식으로 한 가지의 방법만 고집하기 보단 상황에 맞는 렌더링 방법과 서버/클라이언트 컴포넌트를 잘 선택해야합니다.

마무리

이번 글에서는 서버 컴포넌트에 대해서 다뤘는데요. 애매했던 서버 사이드 렌더링과 서버 컴포넌트의 개념을 확실하게 정리한 거 같아서 좋았습니다. Client Boundary 부분도 공공식 문서를 통해 공부했던 내용이 완전히 이해되지 않았었는데 이번 영상을 통해 확실하게 알게되어서 짧지만 도움이 많이 되었던 영상이었습니다. children을 통해서 Client Boundary를 피하는 방법은 알고 있었지만 그 이유에 대해서 몰랐었는데요. 부모/자식이 중요한 것이 아니라 import를 하는 컴포넌트가 중요하고 해당 컴포넌트의 렌더링을 결정하는 컴포넌트는 App 컴포넌트이고 ColorProvider 컴포넌트는 해당 컴포넌트를 보여주는 통로의 역할을 하기 때문인 걸로 이해했습니다.

Reference

profile
흑우 모르는 흑우 없제~

0개의 댓글