[NextJS] React Server Components(RSC)

yongkini ·2024년 1월 24일
0

NextJS

목록 보기
4/4
post-thumbnail

React Server Components(RSC)

intro

: 현재 링크에 해당하는 nextjs 공식 문서를 통해 공부를 하고 있고, 그 와중에 얻게된 지식을 한 번 정리하고 가는게 좋을 것 같아서 글을 쓴다. 이 글은 나중에 nextJS를 쓰면서 돌아와서 수정이 될수도 있고, 분명 틀린 부분이 있을 수 있다. 하지만, 지금까지 내가 이 부분을 공부하면서 느낀 것 및 얻은 것을 한 번 정리하고 간다는 측면에서 의의가 있다고 본다.

React Server Component?

: 본래 React로 개발을 할 때는 모든게 client component였다. 번들링된 app.js 등을 브라우저가 받아서 client component를 브라우저 단에서 렌더링 했었다. 하지만, nextjs로 오면 서버 단에서 data를 fetching하고(기존에는 client에서 api를 호출하고, 서버 단에서 db와 통신했다 with ORM 등), 서버 단에서 렌더링을 하여 client에 완성된 html을 보내는 형태가 된다. 이런 부분에서 nextjs는 SSR 쪽에 가깝다. 어쨌든, nextjs에서는 기본적으로 이런 server component를 사용하고, client component를 쓰기 위해서는 'use client' 라는 키워드를 써줘야만 한다. 즉, 앞서 말한 것처럼 디폴트가 서버 컴포넌트다.

Static Rendering 과 Dynamic Rendering 그리고 Streaming

  • Static Rendering은 심플하게 빌드 단계에서 이미 서버 컴포넌트에서 이뤄지는 data-fetching 그리고 렌더링이 끝난 상태를 의미한다. 그러면 유저가 배포된 URL을 입력하여 접속하면? 이미 빌드 단계에(배포 단계라고 할수도 있겠다) 완성돼있던 페이지를 보내줄 뿐이다. 또한, static 하기 때문에 cdn에 캐싱하여 보관하여 주기 때문에(첫 요청 이후) 속도도 빠르고, 서버 부하가 현저히 줄어든다.

  • Dynamic Rendering : Static Rendering은 심플하게 업데이트가 없는 '나무위키'를 생각하면 된다. 하지만, 보통은 유저 별 정보를 바탕으로 페이지 구성이 달라지거나, 유저 인터랙션에 따라서도 빠르게 정보를 업데이트 해야하는 서비스가 있는데, 이 경우 Dynamic Rendering을 사용해야 한다. 즉, 해당 컴포넌트에서 쓰는 Data-Fetching은 Static-Rendering처럼 빌드시에 한번 하고 말아도 되는 정보들이 아닌 유저가 요청을 보내는 상황에 맞게 혹은 유저가 보내는 파라미터에 맞게 유동적으로 재호출하고, 갱신해야 하는 정보들에 대한 fetching인거다. 따라서, Dynamic-Rendereing을 하면 유저가 요청을 할 때 데이터를 유동적으로 새로 받고, 렌더링을 해서 client에 보내게 된다(이 때도 서버에서 렌더링을 하는건 서버 컴포넌트로서는 마찬가지다). 하지만, 이 때, 문제가 있는데, 만약에 특정 페이지에서 3개의 Data-fetching 로직을 쓰는데, 그 Data-Fetching 로직이 상당히 느리다고 가정해봤을 때, 뒤에서 말할 Streaming을 쓰지 않으면 그 시간만큼 해당 페이지 자체가 혹은 해당 컴포넌트가 속한 페이지 자체가 blocking 돼 아무것도 렌더링 되지 않게 되는 시간이 생긴다. 다시 말해, fashion item fetching 로직이 3초 걸린다고 했을 때, fashion item 렌더링 부분만 3초동안 안보이거나 로딩 fallback component가 나오는게 아니라 해당 컴포넌트를 포함한 페이지 자체가 3초간 blocking이 되는거다. 그래서 dynamic-rendering에서는 가장 느린 data-fetching 로직의 시간만큼 렌더링이 지연된다는 문제가 있다.
    ** 참고로 확실하진 않지만, data-fetching 로직이 포함돼 있으면 자동으로 dynamic-rendering을 하고, 아닌 경우 static-rendering으로 처리하는 것 같다. 영어로 치기 귀찮아서 한글로 물어봤는데, 어쨌든 chatGPT의 답변은 맞다 이다.
    dynamice-rendering을 하게 하는 방법:
    - noStore() 메서드를 data-fetching 함수마다 써주면 된다(experimental api 임을 참고).

    export const dynamic = "force-dynamic".

    이게 좀더 정석적인 방법인 것 같다.
    +a :

    Currently, if you call a dynamic function inside your route (e.g. noStore(), cookies(), etc), your entire route becomes dynamic.

  • Streaming : 위의 dynamic-rendering의 문제는 'streaming'으로 해결할 수 있다.
    위의 gif는 streaming을 잘보여주는 예시라 가져와봤다(실제로 nextjs tutorial을 통해 만들어볼 수 있다). 앞서 말한 dynamice-rendering의 문제였던 모든 페이지가 blocking되는 것이 아닌 rendering 할 수 있는 것들을 병렬적으로 처리하여 렌더링한다.

    ** reference : https://nextjs.org/learn/dashboard-app/streaming

공식 문서에 나온 이미지인데, 이게 심플하게 Streaming이 해주는 역할과 뭘 말하는건지를 알 수 있게 해줘서 가져와봤다.

그럼 이러한 Streaming은 실제로 어떻게 구현하는걸까 ?

There are two ways you implement streaming in Next.js:

  • At the page level, with the loading.tsx file.
  • For specific components, with .

하지만 이러한 Streaming 기법에 정답은 없다. 아래 gif처럼 loading.tsx 를 사용해서 전체를 기다렸다가 한번에 보여주는 방법도 있고(좀 기다려야한다),
방법도 간단하다.

두번째 방법처럼 Suspensese를 통해 분할해주는 방법도 있다.

import CardWrapper from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
import { Suspense } from 'react';
import {
  RevenueChartSkeleton,
  LatestInvoicesSkeleton,
  CardsSkeleton,
} from '@/app/ui/skeletons';

export default async function Page() {
  return (
    <main>
      <h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
        Dashboard
      </h1>
      <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
        <Suspense fallback={<CardsSkeleton />}>
          <CardWrapper />
        </Suspense>
      </div>
      <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
        <Suspense fallback={<RevenueChartSkeleton />}>
          <RevenueChart />
        </Suspense>
        <Suspense fallback={<LatestInvoicesSkeleton />}>
          <LatestInvoices />
        </Suspense>
      </div>
    </main>
  );
}

이 때, 위에서 말한 것처럼 정답은 없다. 모든 컴포넌트를 Suspense에 넣어놓어도 좋을 UX가 있다면 그렇게 하면 되고, loading.tsx를 이용해 한꺼번에 보여주는 방법이 맞는 페이지라면 그렇게 하면 된다. 아래처럼 각각의 장단점이 있다.

  • You could stream the whole page like we did with loading.tsx... but that may lead to a longer loading time if one of the components has a slow data fetch.
  • You could stream every component individually... but that may lead to UI popping into the screen as it becomes ready.
  • You could also create a staggered effect by streaming page sections. But you'll need to create wrapper components.

** 추가로 이해에 도움되는 참고 이미지

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글