본문으로 바로가기

React Server Component (RSC)

category software engineering/frontend 2023. 4. 15. 01:33
728x90

지난 use() 훅에 관한 포스트에서 나왔던 주제인 React Server Component, Client Component, Shared Component 에 관해 다뤄보겠습니다.

 

요약

2020년 12월 21일, React 팀에서 React Server Components 를 공개했습니다. React Server Component 는 클라이언트 사이드의 풍부한 인터렉티비와 전통적인 서버사이드 렌더링의 성능적 이점을 결합하여 다음과 같은 효과를 기대할 수 있습니다.

  • 서버에서만 실행되어 번들 크기에 전혀 영향을 주지 않습니다. 클라이언트에서 다운로드 되지 않아 번들 크기가 줄어 시작 시간 개선에 도움이 됩니다.
  • 데이터베이스, 파일 시스템, 마이크로 서비스 등 서버측 데이터 소스에 엑세스 할 수 있습니다.
  • 기존 클라이언트 컴포넌트와 원활하게 통합됩니다. 서버에서 데이터를 로드하고 클라이언트 컴포넌트에 prop으로 전달하여 페이지의 렌더링을 처리합니다.
  • 렌더링할 클라이언트 컴퍼넌트를 동적으로 선택할 수 있으므로 클라이언트는 페이지를 렌더링하는 데 필요한 최소한의 코드만 다운로드할 수 있습니다.
  • 다시 로드될 때 클라이언트 상태를 유지합니다 . 즉, 서버 컴퍼넌트 트리를 다시 가져올 때 클라이언트 상태, 포커스 및 진행 중인 애니메이션이 중단되거나 재설정되지 않습니다.
  • 서버 컴포넌트는 점진적으로 렌더링되고 UI의 렌더링된 단위를 점진적으로 클라이언트에 스트리밍합니다 . Suspense와 결합하면 개발자가 의도적인 로드 상태를 만들고 페이지 의 나머지 부분이 로드되기를 기다리는 동안 중요한 콘텐츠를 빠르게 표시 할 수 있습니다.
  • 개발자는 또한 서버와 클라이언트 간에 코드를 공유 할 수 있으므로 단일 컴퍼넌트를 사용하여 한 경로에서 서버에 있는 일부 콘텐츠의 정적 버전을 렌더링하고 다른 경로에서 클라이언트에서 해당 콘텐츠의 편집 가능한 버전을 렌더링할 수 있습니다.

 

사용법

useEffect, useState 와 같은 클라이언트 컴포넌트에서만 사용할 수 있는 요소를 사용할 수 없지만 prop을 통해 전달 받아 사용하며 전반적으로 예상하는 시나리오대로 잘 작동합니다. 데이터베이스와 같은 서버에 직접 접근할 수 있습니다. "클라이언트" 컴포넌트를 가져오고 렌더링을 전달할 수 있습니다. 이를 위해 'use client' 를 파일 맨 위에 지시하면 됩니다.

 

// Note.js - Server Component

import db from 'db'; 
// (A1) We import from NoteEditor.js - a Client Component.
import NoteEditor from 'NoteEditor';

async function Note(props) {
  const {id, isEditing} = props;
  // (B) Can directly access server data sources during render, e.g. databases
  const note = await db.posts.get(id);
  
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {/* (A2) Dynamically render the editor only if necessary */}
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}
// NoteEditor.js - Client Component

'use client';

import { useState } from 'react';

export default function NoteEditor(props) {
  const note = props.note;
  const [title, setTitle] = useState(note.title);
  const [body, setBody] = useState(note.body);
  const updateTitle = event => {
    setTitle(event.target.value);
  };
  const updateBody = event => {
    setTitle(event.target.value);
  };
  const submit = () => {
    // ...save note...
  };
  return (
    <form action="..." method="..." onSubmit={submit}>
      <input name="title" onChange={updateTitle} value={title} />
      <textarea name="body" onChange={updateBody}>{body}</textarea>
    </form>
  );
}

 

 

Dan & Lauren's talk  

UI development 에 있어서 중요한 세가지 요소는 다음과 같다. 좋은 UX, 쉬운 유지보수성, 빠른 퍼포먼스입니다. 

Good UX

ArtistPage 에 접근했을 때 ArtistDetails, TopTracks, Discography 컴포넌트가 동시에 렌더링 되거나 혹은 원하는 순서에 맞게 렌더링이 되길 원할 것이고, 무작위 순서로 매번 다른 순서로 렌더링이 되길 원치 않을 것입니다.

 

Cheap Maintenance

만약 아래와 같이 API 호출이 필요한 경우 fetch 이후 props 을 아래와 같이 전달해줄 수 있습니다. 문제는 해당 컴포넌트와 API response 가 강하게 결합된 상태라는 점입니다. 모든 상황에서 필요한건 아니지만 component 단위가 거대해진 경우 이전 위에 소개된 상태로 돌아가기 위해 각 하위 컴포넌트에서 fetchDetails, fetchTopTracks, fetchDiscogrpahy 를 통해 coupling 을 낮출 수 있습니다. 하지만 새롭게 무언가 생기거나 변경 될 때마다 root 로 돌아가서 context 를 통해 변경하거나 props 전달되는 상황이 유지보수 측면에서 불편하게 느껴지겠죠.

Spotify Demo - Cheap Maintenance

 

Fast Performance

그러나 위와 같이 각 Component 에서 API 호출을 한다면 빠른 퍼포먼스를 제공하진 않습니다. Waterfall 방식으로 spinner가 돌고 클라이언트가 서버에 요청하고 응답하고 다시 요청하고 응답하기 때문입니다. 무조건적으로 세 가지를 만족 해야하진 않지만 이렇게 되길 원하고 세가지 조건을 모두 만족하기 또한 쉽지 않습니다.

 

사실 이 문제는 Facebook 팀 내에선 Relay 와 GraphQL 을 통해 해결했습니다. Relay를 통해 필요한 데이터를 한번에 묶어서 요청하고 GraphQL을 통해 필요한 데이터를 가져왔지만 작은 규모의 프로젝트나 굳이 크게 강점을 갖기 힘든 경우 GraphQL Backend 를 설정하거나 레거시가 강하다거나 API를 expose 해야하는 등 다양한 이유로 이 방법이 필수적인 요소는 아닙니다.

 

 

Waterfall 방식으로 구성되어 있어 클라언트와 서버간 소통이 지연되는 이유로 React Component 를 서버로 이동시키고 backend 와 빠르게 데이터 통신을 하고 나중에 Client 에 Response를 전달해주는 방법으로 React Ecosystem 에 필요한 대부분의 언급했던 문제를 해결할 수 있습니다. 이것을 React Server Component 라고 부릅니다.

 

GitHub 데모 살펴보기

번들 크기가 0 인 컴포넌트

서버 컴포넌트 를 사용하면 위의 문제 뿐만 아니라 다른 문제들도 해결할 수 있습니다. 예를 들어, date-fns 라이브러리를 통해 format을 변경하는 코드 혹은 마크 다운을 렌더링하기 위해 라이브러리를 import 하는 경우, 일반적인 클라이언트 컴포넌트 를 사용하면 브라우저에서 유저에게 다운로드를 시키지만 React Server Component를 사용하면 서버에서 다운로드 받고 Webpack Gzip으로 압축되기에 훨씬 효과적입니다. Markdown으로 렌더링 하려면 240K 이상의 JS가 필요할 수 있습니다(개별적으로 gzip으로 압축된 74K 이상).

 

 

백엔드 전체에 대한 엑세스

예를 들어 데이터를 저장할 위치가 확실하지 않은 경우 파일 시스템에서 시작할 수 있습니다.

import fs from 'fs';

async function Note({id}) {
  const note = JSON.parse(await fs.readFile(`${id}.json`));
  return <NoteWithMarkdown note={note} />;
}

보다 정교한 앱은 데이터베이스, 내부(마이크로)서비스 및 기타 백엔드 전용 데이터 소스를 사용하기 위해 직접 백엔드 액세스를 유사하게 활용할 수 있습니다.

 

코드 자동 분할

일반적으로 Code Splitting 을 하기 위해서 개발자는 dynamic import 를 사용해야 한다는 점을 기억해야 합니다. 그리고 해당 컴포넌트에 도달했을 때 로드 하지만 서버 컴포넌트에서는 자동으로 Code Splitting 을 제공하고 동적으로 다운로드 받는게 아니라 렌더링 초기에 다운로드 받을 수 있습니다. 이러한 기능을 통해 개발자가 앱에 더 집중하고 자연스러운 코드를 작성할 수 있습니다.

 

Waterfall 방식으로의 해방

일반적으로 퍼포먼스를 저하시키는 이유는 데이터를 가져올 때 useState 에 초기화하고 useEffect 에서 부모 컴포넌트로부터 prop.id 와 같은 dependency 를 전달 받고 데이터를 가져오고 state를 업데이트 하는 방식입니다. 필요한 데이터를 정확하게 갖고 오고 렌더링 되지 않은 부분의 데이터를 방지할 수 있지만 부모 컴포넌트가 데이터 로드를 완료할 때까지 자식 컴포넌트는 데이터를 가져올 수 없습니다.

 

서버 컴포넌트를 사용하면 서버로 직접 이동하여 클라이언트와 서버간 순차 왕복을 줄여 요청 대기 시간이 줄고 성능이 향상될 수 있습니다.

 

제약 사항

서버 컴포넌트에선 interactivity 를 사용할 수 없는 제약이 있습니다. useState 나 event listener 와 같은 것들을 사용할 수 없기에 만약 toggle과 같은 interactivity 를 사용하고 싶다면 클라이언트 컴포넌트에 관련 로직을 몰아 넣고 import 해서 사용할 수 있죠. 그리고 클라이언트에 prop을 전달할 때는 serializable 해야한다는 조건이 있습니다. JSX는 serializable 하지만 () => {} 와 같은 함수는 그렇지 않아 에러를 throw 합니다.

 

❌ useEffect, useLayoutEffect, useState, useReducer, DOM 과 같은 브라우저 전용 API, custom hook, 브라우저 전용 API

 

Suspens 와 통합

Server Components 데이터 가져오기 API는 Suspense와 통합됩니다. 전체 응답이 완료되기 전에 클라이언트가 무언가를 보여줄 수 있도록 Suspense를 사용하여 로드 상태를 제공하고 스트림의 일부를 차단 해제합니다.

 

서버 컴포넌트, 클라이언트 컴포넌트, 공용 컴포넌트

Server Component 는 초기에 Note.server.js, Client Component는 Note.client.js, Shared Component는 Note.js 로 나타냈습니다 (현재는 'use client' 를 파일 맨위에 명시하여 사용합니다). Shared Component 가 .js 만으로 표현한 이유는 Server Component 에서 import 된다면 Server 에서 다운로드 받고 Client 에서 다운 받지 않고 반대도 마찬가지로 한 부분에서만 다운로드 받지만 양쪽에서 사용할 수 있도록 유동적으로 하기 위해서 입니다.

 

인터렉티비티가 필요한 부분은 클라이언트 에서, data-fetching 이나 pre-processing 이 필요한 코드는 Server Component에서 그리고 이 사이에 특별한 방해물이 없기에 두 군데서 사용될 코드는 Shared Component 로 사용함으로써 좀 더 각 영역에 집중하고 좋은 퍼포먼스를 낼 수 있습니다.

웹팩 파일을 들여다보면 Server Component 는 보이지 않고 Shared Component 와 Client Component 를 볼 수 있습니다. 또한 서버 컴포넌트 내에서 실제 어플리케이션이라면 데이터 추상화 레이어를 제공함으로써 데이터를 접근하는데 안정적으로 짜겠지만 raw query 를 서버 컴포넌트에서 아래와 같이 사용할 수는 있습니다.

 

 

이러한게 가능한 이유는 서버 컴포넌트는 HTML을 렌더링하지 않고 위와 같은 특별한 포맷으로 렌더합니다. 이러한 포맷으로 server tree 의 첫번째 부분을 우선적으로 클라이언트에 전달하여 응답을 토대로 로딩을 보여줄지 등을 결정합니다. 

 

IO 라이브러리

Node 에서 사용하던 라이브러리를 wrapping 하여 미니멀한 버전의 라이브러리를 아래와 같이 제공합니다.

 

SSR 과 Server Component

  • 서버 구성 요소에 대한 코드는 클라이언트에 전달되지 않습니다. React를 사용하는 SSR의 많은 구현에서 구성 요소 코드는 어쨌든 JavaScript 번들을 통해 클라이언트로 전송됩니다. 이로 인해 상호 작용이 지연될 수 있습니다.
  • 서버 구성 요소를 사용하면 트리의 어느 곳에서나 백엔드에 액세스할 수 있습니다. Next.js를 사용할 때 최상위 페이지에서만 작동하는 제한이 있는 getServerProps()를 통해 백엔드에 액세스하는 데 익숙합니다. 임의의 npm 구성 요소는 이 작업을 수행할 수 없습니다.
  • 서버 구성 요소는 트리 내에서 클라이언트 측 상태를 유지하면서 다시 가져올 수 있습니다. 이는 기본 전송 메커니즘이 HTML보다 훨씬 풍부하기 때문에 내부 상태(예: 검색 입력 텍스트, 포커스, 텍스트 선택)를 날려버리지 않고 서버에서 렌더링된 부분(예: 검색 결과 목록)을 다시 가져올 수 있기 때문입니다.

서버 구성 요소에 대한 초기 통합 작업 중 일부는 다음과 같은 webpack 플러그인을 통해 수행됩니다.

 

https://beta.nextjs.org/docs/rendering/server-and-client-components

 

Rendering: Server and Client Components | Next.js

Learn how to use React Server and Client Components in your Next.js application.

beta.nextjs.org

https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md

 

GitHub - reactjs/rfcs: RFCs for changes to React

RFCs for changes to React. Contribute to reactjs/rfcs development by creating an account on GitHub.

github.com

https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components

 

Introducing Zero-Bundle-Size React Server Components – React

The library for web and native user interfaces

react.dev

https://github.com/reactjs/server-components-demo

 

GitHub - reactjs/server-components-demo: Demo app of React Server Components.

Demo app of React Server Components. Contribute to reactjs/server-components-demo development by creating an account on GitHub.

github.com

https://www.patterns.dev/posts/react-server-components

 

React Server Components

Server Components compliment SSR, rendering to an intermediate abstraction without needing to add to the JavaScript bundle

www.patterns.dev

 

'software engineering > frontend' 카테고리의 다른 글

React 18 매력적인 기능  (0) 2023.05.01
TL;DR shadow DOM  (0) 2023.04.15
React - Use() Hook  (0) 2023.03.29
React- 어떤 상태 관리 라이브러리를 사용할까?  (0) 2023.03.27
React v18.0  (1) 2023.03.09