React, Next.js에서의 Suspense

이수빈·2023년 10월 26일
3

Next.js

목록 보기
9/15
post-thumbnail
  • 이전 글에서 React에서 Suspense 동작원리를 정리한 포스트를 작성했었다.

  • 핵심은 Promise를 상위 컴포넌트로 던지는 것!!

ref) https://velog.io/@bnb8419/Suspense%EC%99%80-lazy-Loading

React에서의 Suspense 활용

Suspense with React.lazy

  • 가장 대표적인 활용이 코드 스플릿팅을 통한 번들사이즈 감소이다.

  • React.lazy를 통해 자식 컴포넌트를 분할한다. 컴포넌트가 필요할 때 Promise의 상태(pending, fullfilled..) 에 따라 상위 컴포넌트로 Promise를 던진다.

  • pending 상태일땐 fallback을, fullfilled일때는 자식컴포넌트를 렌더링한다.

Suspense with Hydration

  • React 18이전에서 Hydration은 보통 다음과 같은 단계로 이루어진다. (Suspense 적용 이전)

  • JS파일 전체를 불러온 후 다시 리렌더링하는 작업이 실행됨. (원문기준으로 3가지 존재)

  • 유저와 상호작용이 정말 많은 페이지라면? 유저가 실제로 상호작용 할 수 있는 TTI의 시점이 그만큼 늦어진다는 의미임

  • 18V 이전에는 Suspense를 Server Side에서 사용 할 수 없었음.

서버에서 전체 애플리케이션에서 사용할 데이터를 가져온다.
그 후, 서버에서 애플리케이션을 HTML로 렌더링한 후 응답(response)로 보낸다.
그 후, 클라이언트에서 JavaScript를 불러온다.
그 후, 클라이언트에서 서버에서 생성된 HTML에 JavaScript 로직을 연결시킨다.

기존 SSR방식의 문제점(원문번역참조)

서버에서 html을 렌더링하기전에 app에 필요한 모든 데이터를 가져와야함.

  • 오늘날의 SSR이 가진 문제 중 하나는 컴포넌트로 하여금 “데이터를 기다리도록” 하지 않는다. 현재 제공되는 API를 사용하면 HTML에 렌더할 때 서버상에서 컴포넌트에 필요한 데이터를 모두 다 준비해놔야 한다. 이 뜻은 클라이언트에 HTML을 보내기 전에 서버상에서 모든 데이터를 모아놔야 한다는 것이다. 이 방법은 꽤나 비효율적이다.

  • 예를들어, 댓글이 있는 글을 렌더링하고 싶다고 가정해보자. 댓글은 이른 시기부터 보여주는 것이 중요하기 때문에, 서버사이드 HTML 출력에 추가하고 싶다. 하지만 DB나 API 레이어의 속도가 느린데 이건 건드릴 수 없는 상황이다. 이럴 경우 힘든 결정을 내려야 한다. 서버 출력물에서 제외하면 유저는 JS가 완벽히 불러와지기 전까지 볼 수 없을 것이다. 하지만 서버 출력에 포함시키면 댓글이 불러와지고 전체 트리를 렌더하기 전까지 나머지 HTML을 전송하는 것을 지연시켜야 한다 (네비게이션바, 사이드바, 그리고 심지어 포스팅 본문까지도 여기에 포함된다). 이건 좋지 않다.

  • 한 가지 덧붙히자면, 몇몇 데이터를 가져오는 방법들은 데이터가 완전히 불러와지기 전까지 트리를 HTML에 렌더하고 결과물을 버리는 방식을 반복적으로 수행한다. 이는 React가 더 좋은 옵션을 제공하지 않기 때문이고, 우리는 이런 극단적인 타협책을 요구하지 않는 방법을 제시하고자 한다.

hydrate를 시작하기 전에 모든 JS코드가 로드되어야 한다.

  • JavaScript 코드가 불러와진 후, React에게 HTML을 “하이드레이트”하라 지시하고, 이를 통해 페이지는 상호작용이 가능한 상태가 된다. React는 컴포넌트를 렌더링하는 과정 중 서버사이드에서 생성된 HTML을 순회하며 이벤트 핸들러를 붙혀준다. 이게 동작하기 위해 브라우저에서 컴포넌트를 기반으로 생성된 트리(tree)가 서버에서 생성된 트리와 일치하여야 한다. 그렇지 않으면, 리액트는 말 그대로 “일치시킬 수”없다! 가장 안타까운 것은 어떠한 하이드레이션도 시작하기 전에 모든 컴포넌트를 대상으로한 JavaScript가 클라이언트상에 완전히 불러와져야 한다는 점이다.

  • 예를들어, 댓글 위젯은 많은 양의 복잡한 상호작용 로직을 가지고 있고, JavaScript를 불러오기 위해 꽤나 오랜 시간이 걸린다고 가정하자. 다시 힘든 결정을 내려야 한다. 유저에게 이른 시기부터 보여주기 위해 댓글을 서버상에서 HTML로 렌더하는 것이 좋을 것이다. 하지만 오늘날의 하이드레이션은 단일 작업만 가능하기에, 네비게이션바, 사이드바, 그리고 포스트 본문들까지 댓글 위젯에 대한 코드가 불러와지기 전까지 하이드레이션을 할 수 없다. 물론 코드 스플리팅을 통해 따로따로 로드할 수도 잇지만, 이럴 경우 서버 HTML에 있는 댓글을 빼줘야 할 것이다. 그렇지 않으면 React는 HTML의 일부(chunk)만 가지고 무얼 해야할지 모르고 (이 코드는 어디로 가는 것일까?) 하이드레이션 단계에서 해당 코드를 삭제할 것이다.

상호작용을 시작하기 전 모든 html에 hydrate가 완료되어야 한다.

  • 하이드레이션 자체에도 비슷한 문제가 있다. 오늘날 React는 트리를 한 번의 작업을 통해 하이드레이션을 진행한다. 이 뜻은, 하이드레이션을 한 번 시작하면 (말하자면 컴포넌트 함수를 호출하는 과정), React는 전체 트리에 대해 이 과정을 완료하기 전까지 멈추지 않는다. 결과적으로, 컴포넌트 중 어느 하나라도 상호작용 하기 위해서는 모든 컴포넌트가 하이드레이션 되어야 한다.

  • 예를들어, 댓글 위젯쪽에 굉장히 시간이 오래 걸리는 렌더링 로직이 들어있다고 가정하자. 본인의 컴퓨터에서는 빠르게 동작할 수 있지만, 저사양 디바이스에서는 모든 로직을 실행하는 것이 빠르지 않고, 심지어 몇 초간 화면을 고정시킬 수도 있다. 물론, 이상적으로 클라이언트 사이드에 이런 로직은 없을 것이다 (그리고 이런 경우를 대비하기 위해 Server Component가 개발되고 있는 것이다). 하지만 몇몇 로직은 부착된 이벤트 핸들러의 작업을 결정하고 상호작용에 필수적이기 때문에 이런 상황이 불가피하다. 결과적으로 한 번 하이드레이션이 시작되면 전체 트리가 완전히 하이드레이션 되기 전까지 유저는 네비게이션 바, 사이드바, 포스팅 본문과 상호작용할 수 없다. 특히나 네비게이션의 경우 유저가 이 페이지 자체에서 떠나고 싶지만 현재 클라이언트에서 열심히 하이드레이션을 진행하고 있기 때문에 더 이상 보고 싶지 않은 페이지에 남아 있어야 하는 굉장히 안좋은 케이스다.

React 18버전의 Suspense

  • React 18버전에서는 Suspesne를 이용한 2개의 ssr기능이 추가됨.

Streaming HTML & Selective Hydration

  • 서버에서 HTML을 스트리밍 형식으로 전달하는 기능 => renderToString을 renderToPipeableStream로 바꿔주는 방식으로 가능함.

아래와 같은 기능이 존재(관련PR)

Full built-in support for (which integrates with data fetching)
Code splitting with lazy without flashes of "disappearing" content
Streaming of HTML with "delayed" content blocks "popping in" later

  • Suspense로 컴포넌트를 감싸고 동적으로 import함. => SSR시 HTML을 스트리밍하고, 선택적 하이드레이션이 가능함. (Suspense로 감싼 컴포넌트는 SSR 방식에서 자동으로 fallback 및 hydration을 처리해줌)
import { lazy } from "react";

const Comments = lazy(() => import("./Comments.js"));  
  
<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>
  • Comments를 Suspense로 묶음으로써 스트리밍과 하이드레이션이 지연요소로 인해 블로킹 되는것을 막아줌.

  • Comment가 불러오지 않은 상태에서도 나머지 불러온 부분에 대해 hydration을 진행함.
    Comment가 완벽히 로드되었을 때 선택적으로 hydration 적용

  • 또한 Suspense로 컴포넌트를 감싸면 하이드레이션 과정 자체가 더 이상 다른 작업을 할 수 없게 브라우저를 점유하지 않음

Next.js의 Hydration & Suspense

  • Hydration은 Next.js만의 특별한 동작이 아니라 ReactDOM함수임.

  • ReactDOM.hydrate 함수를 사용해 하이드레이션 진행

ReactDOM.render(element, container, [callback]);

  • ReactDOM.render() 함수는 특정 컴포넌트를 두 번째 인자로 지정된 DOM 요소에 하위로 주입하여 렌더링을 처리해주는 함수이다.
  • 제공된 container에 element를 렌더링하고, component에 대한 참조를 반환한다.
  • 만약 이전에 element가 container에 렌더링 됐었다면, ReactDOM을 업데이트하고, 최종 - React element의 반영이 필요할 때에만 DOM을 변경한다.
  • 렌더링이 완료되면 특정 이벤트를 처리하는 콜백 함수를 세 번째 인자로 넘겨줄 수 있다.
  • container node를 수정하지 않고 자식만 수정한다.
  • 기존 자식을 덮어쓰지 않고 구성 요소를 삽입한다.

ReactDOM.hydrate(element, container, [callback]);

  • render() 메서드와 동일하지만 ReactDOMServer에서 HTML 컨텐츠를 렌더링한 container를 hydrate하기 위해 사용된다.
  • SSR을 통해 이미 마크업이 채워져 있는 경우 다시 렌더링할 필요가 없으므로 hydrate를 통해 기존 마크업에 이벤트 리스너 등만 추가한다.
  • 특정 컴포넌트를 두 번째 인자로 지정된 DOM 요소에 하위로 hydrate 처리한다.
  • 렌더링을 통해 새로운 웹 페이지를 구성할 DOM을 생성하지 않는다.
  • 대신, 기존 DOM tree에서 해당되는 DOM 컴포넌트를 찾아 정해진 JS 속성(예: eventListener)만 부착시킨다.

next/dynamic(공식문서 )

  • next.js에서 컴포넌트 동적로딩을 구현하는 방법은 두가지가 존재한다.

  • Default로 Server Component일 경우 자동적으로 코드스플릿팅이 적용된다. ( streaming & hydration 자동적용)

Server Component란?
서버 컴포넌트에 관한 자세한 내용은 아래 두 블로그를 참고하자
ref) https://haesoo9410.tistory.com/404
ref) https://velog.io/@sententia/React-Server-Component-RSC

  • 클라이언트 컴포넌트에게 동적로딩을 적용가능함.

  • React.lazy + Suspense 조합과 Dynamic Import를 사용하는 방법이 있음.

next/dynamic

  • next/dynamic is a composite of React.lazy() and Suspense.

  • It behaves the same way in the app and pages directories to allow for incremental migration.

  • fallback 설정 및 ssr : false로 설정하면 클라이언트 사이드에서 렌더링됨.

import dynamic from 'next/dynamic'
 
const DynamicHeader = dynamic(() => import('../components/header'), {
  loading: () => <p>Loading...</p>,
    ssr: false,

})
 
export default function Home() {
  return <DynamicHeader />
}

ref) https://velog.io/@lky5697/suspense-in-different-architectures

ref) https://immigration9.github.io/react/2021/06/13/new-suspense-ssr-architecture.html

ref) https://velog.io/@hamjw0122/Next.js-Hydration

profile
응애 나 애기 개발자

0개의 댓글