Next에서 loading에 해당하는 UI 구성방법

을 알아보기 전에 React에서의 처리 방법을 알아보자!!

React에서는 어케 하지?

이전에는 TQ의 isLoading 같은 props로 로딩일 경우를 따로 분기처리해줬지만

react v18에서 릴리즈된 Suspense라는 기능을 사용해서 loading처리 선언적으로 해줄 수 있다.

Suspense

Suspense는 크게 fallback과 children으로 구분해서 사용할 수있다.

  • fallback : 로딩이되는 동안 보여줄 UI
  • children : 로딩의 유무를 정하는 컴포넌트

사용방법은

<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>

이러한 형태로

<SomeComponent /> 가 로딩되는 동안은 <Loading /> 을 보여주고 로딩이 완료되면 <SomeComponent /> 로 UI를 변경해준다.

하지만 이걸 SSR에서 적용하는 방법은 좀 더 알아야할 동작원리가 있다! (Streaming HTML, Selective Hydration)

이는 뒤에서 다루겠다!

Next에서는 어케 하지?

next 앱 라우터에서는 suspensefallback에 해당하는 loading pageerrorBoundaryfallback에 해당하는 error page를 제공해준다.

loading.tsx

앱 라우터에서 loading.tsx를 사용하게 되면 같은 디렉토리 내의 page.tsxfallback으로 사용된다.

ReactSuspense를 사용한 것 과 동일한효과를 지니며 중첩으로 사용할 수 있어 모든 페이지를 loading으로 대체하는 것이 아닌 해당 디렉토리의 page에 해당하는 부분만 변경된다.

즉, 라우팅된 디렉토리 별로 loading을 중첩해서 사용할 수 있다는 뜻이다!!

loading-overview

streaming

또한 next는 React 18에 정싱 릴리즈 된 Suspense를 사용한 스트리밍을 지원한다.

먼저 스트리밍의 작동 방식을 이해하기 위해 SSR의 과정을 이해해야한다.

SSR 과 CSR 차이 요약

이러한 방식에서 단점은 1,2,3,4 단계가 순차적으로 진행된다는 것이다.

자세히 살펴보면 client는 두가지 불편한 점을 느낄 수 있다.

  1. 1단계 → 2단계 : 서버가 데이터를 모두 패칭할때 까지 client는 아무것도 볼 수 없다.
  2. 3단계 → 4단계 : hydration이 완료 될때까지 client는 어떤한 동작도 할 수 없다.

이러한 단점을 보완하기 위해 나온 개념이 바로 스트리밍이다.

스트리밍의 원리는 HTTP 캐시 무효 전략인 stale-while-revalidate 전략을 사용하는 이유와 비슷하다고 느껴진다. (TQ, SWR도 이방식으로 데이터 패칭을 한다!)

스트리밍의 핵심은 ‘모든’ 데이터가 아닌 ‘일부’를 먼저 보여준다는 것이고 HTML전체를 받아오는게 아니라 여러개의 조각(chunk)로 나눈 후 점진적으로 해당 chunk를 server에서 client에게 보내준다.

이를 시각화하면

SSR 방식 렌더링

server-rendering-without-streaming.png

server-rendering-without-streaming-chart.png

Stream 방식 렌더링

server-rendering-with-streaming.png

server-rendering-with-streaming-chart.png

이렇게 조각조각 보내는 것이다.

이를 통해 ‘전체’를 기다려야 했던 client는 chunk의 우선 순위별로 필요한 것을 먼저 받아 ‘일부’를 우선 렌더링하고 hydration 할 수 있다.

이러한 스트리밍 기법은 TTFB(리소스 요청으로부터 첫번째 응답이 도착하는 시간), FCP(콘텐츠가 포함 된 첫 페인드까지의 시간), TTI(페이지가 유저와 상호작용하는데 걸리는 시간) 를 개선할 수 있고 이는 SEO 점수를 높여 상단에 노출 시킬 수있게된다!!

이를 이용해서 Lighthouse의 성능을 향상 시켜보자!

기존의 mainpage에서는 모든 데이터를 한번에 받아왔다.

const Home = () => {
	...
  return (
    <div className="relative -left-20pxr w-[100vw]">

			...

      <Map
        setCurrentLocation={setCurrentLocation}
        ref={mapRef}
      />

			...

    </div>
  );
};

export default Home;

Untitled

이를 오래걸리는 지도부분을 스트리밍으로 받아오면

const Home = () => {
	...
  return (
    <div className="relative -left-20pxr w-[100vw]">

			...

      <Suspense fallback={<div>Loading...</div>}>
        <Map
          setCurrentLocation={setCurrentLocation}
          ref={mapRef}
        />
      </Suspense>

			...

    </div>
  );
};

export default Home;

Untitled

단 2줄의 코드로 스트리밍을 사용하여

  • 성능점수 : 47점 → 68점
  • LCP : 4.4s → 4.2s
  • TBK : 1,130ms → 270ms

이러한 성능 개선을 맡볼 수 있다!!

여기서 궁금한 점

그러면 Streaming HTML된 요소들의 순서는 어떻게 정하지??

이에대한 답은 New Suspense SSR Architecture in React 18 에서 찾을 수 있었다. Suspense를 통해 선택적으로 hydration하면서 유저가 아직 hydration 되지 않은 html에 상호작용하려고 한다면

react는 우선순위를 바꿔서 이벤트의 캡쳐 단계동안 주석을 동기식으로 hydration 한다

요약하자면

알쓸신잡 할 동작원리!

react는 항상 부모 우선 순서로 hydration한다.

따라서 depth가 깊은 요소에 상호작용을 한다면 이벤트 지점의 전체 상위 트리가 모두 hydration 할때까지 기다려야 한다. 하지만 Suspense의 fallback을 통해 부모요소가 hydration 될때까지 fallback을 띄워 줄 것이기 때문에 코드 분할만 잘 해 놓은다면 오래 기다리지 않아도 된다!

이를 통해 SSR에서 Suspense는 크게 두가지 주요 기능을 제공해준다.

  1. Streaming HTML

    ‘전체’가 아닌 ‘일부(chunk)’를 분할 해서 보내는 방식

  2. Selective Hydration

    관련없는 HTML, JS가 모두 다운로드 되기 전에 필요한 부분만 우선적으로 hydration해서 사용자의 상호작용이 가능한 속도를 향상 시키는 방식

이를 통해 SSR에서의 3가지 문제를 해결 할 수 있다.

  1. 1단계 → 2단계

    더 이상 HTML을 보내기 전에 모든 데이터가 서버에 로드될 때까지 기다릴 필요가 없다.

  2. 3단계 → 4단계

    hydration을 시작하기 위해 모든 JavaScript가 로드될 때까지 기다릴 필요가 없다.

  3. 4단계

    페이지와 상호 작용을 시작하기 위해 모든 구성 요소가 hydration될 때까지 기다릴 필요가 없다.

사실 이건 Suspense의 기능이며 Next에서만 가능한게 아닌 react에서도 SSR을 한다면 가능하다

근데 react에서 SSR 할꺼면… next 쓰는게 이득 아닌가…??


참고

React 공식문서 Suspense

SWR전략

React 18 Suspense SSR

Next : loading UI 및 스트리밍

TTFB

FCP

댓글남기기