react 18의 주요 기능 중 하나인 Suspense가 Server Side Rendering 성능 개선을 위해 어떤 역할을 하게 될지 알아보자
SSR 개요
1. 서버 사이드 렌더링(SSR)은 서버에서 React 컴포넌트를 사용해 HTML을 생성하고, 이 HTML을 사용자에게 전송하는 것을 말한다.
2. SSR은 사용자가 JavaScript bundle이 로드되고 실행되기 전에 페이지의 내용을 볼 수 있게 해준다.
React에서 SSR의 과정
1. 서버에서 전체 app에 대한 데이터를 fetch한다.
2. 그 다음에 서버에서 전체 app을 HTML로 렌더링하여 응답으로 보낸다.
3. 클라이언트에서 전체 app에 대한 Javascript 코드를 로드한다.
4. 클라이언트에서 JavaScript 로직을 서버에서 생성된 전체 app의 HTML에 연결한다.(이 과정을 hydration이라고 한다.)
여기서 중요한 점은 각 단계가 완료되어야 다음 단계가 시작할 수 있다는 것이다.
- 즉 앱의 일부분이 다른 부분보다 느리면 느린 부분이 완료되어야 전체가 완료된다.
- React 18에서는 Suspense를 사용하여 app을 더 작고 독립적인 단위로 나눌 수 있다.
- 이 단위들은 서로 독립적으로 이러한 단계들을 거치며 앱의 나머지 부분을 차단하지 않는다.
- 즉 가장 느린 부분이 빠른 부분에 영향을 주지 않고 먼저 완료되는 것부터 내리기 때문에 사용자들은 컨텐츠를 더 빨리 볼 수 있고 상호작용할 수 있다.
SSR을 자세히 살펴보자
1. 사용자가 app을 로드할 때 가능한 빠르게 완전히 상호작용 가능한 페이지를 보여주어야 한다.
2. 위의 그림에서 페이지의 해당하는 부분들이 상호작용 가능함을 나타내기 위해 녹색을 사용했다.
3. 상호작용이 가능하다는 것은 모든 Javascript 이벤트 핸들러가 연결되어 있으며 버튼을 클릭하면 상태가 업데이트 되는 등의 작업이 가능하다는 것을 의미한다.
4. 그러나 페이지의 JavaScript 코드가 완전히 로드되기 전까지는 페이지를 상호 작용할 수 없다.
5. 이는 React 자체와 애플리케이션 코드를 모두 포함한다.
6. 무거운 app의 경우 로딩 시간의 상당 부분이 애플리케이션 코드를 다운로드하는 데 소요된다.
7. SSR을 사용하지 않는 경우, Javascript가 로드되는 동안 사용자가 볼 수 있는 것은 빈 페이지뿐이다.
8. 이것은 좋지 않은 상황이며 서버 사이드 렌더링(SSR)을 권장하는 이유이다.
9. SSR을 사용하면 React 컴포넌트를 서버에서 HTML로 렌더링하여 사용자에게 전송할 수 있다.
10. HTML은 링크나 폼 입력과 같은 간단한 내장 웹 상호작용을 제외하고 그 자체로는 매우 상호작용적이지 않다.
11. 하지만 이는 JavaScript가 여전히 로딩 중일 때 사용자에게 무언가를 보여줄 수 있게 한다.
12. 여기에서 회색은 화면의 이 부분들이 아직 완전히 상호작용할 수 없음을 나타낸다.
13. app에 대한 JavaScript 코드가 아직 로드되지 않았기 때문에 버튼을 클릭해도 아무 일도 일어나지 않는다.
14. 컨텐츠가 많은 웹사이트의 경우, SSR은 사용자가 JavaScript가 로드되는 동안 컨텐츠를 읽거나 볼 수 있게 해주므로 매우 유용하다.
15. React와 application code가 모두 로드되면, 이 HTML을 상호작용 가능하게 만들어야한다.
16. React에게 서버에서 이 HTML을 생성한 컴포넌트들을 알리고 그 HTML에 이벤트 핸들러를 연결하도록 한다.
17. React는 컴포넌트 트리를 메모리에 렌더링하고, DOM 노드를 생성하는 대신 기존 HTML에 모든 로직을 연결한다.
18. 컴포넌트를 렌더링하고 이벤트 핸들러를 연결하는 이 과정을 hydration이라고 한다.
19. 이는 건조한 HTML에 이벤트 핸들러를 추가하여 상호작용성이라는 수분을 공급하는 것과 같다.
20. hydration 이후 컴포넌트는 상태를 설정할 수 있고, 클릭에 반응할 수 있게 된다.
21. SSR은 일종의 트릭으로 볼 수 있다.
22. 이것은 앱을 더 빨리 완전히 상호작용 가능하게 만들어주지는 않지만 사용자가 JS 로드를 기다리는 동안 정적 콘텐츠를 볼 수 있도록 한다.
23. 이 트릭은 네트워크 연결이 좋지 않은 사람들에게 빈 화면을 보여주는 대신 실제 컨텐츠가 있는 페이지를 바로 볼 수 있기 때문에 페이지가 더 빠르게 로드 되는 것처럼 느껴지게 한다.
24. 또한 쉬운 인덱싱과 더 나은 속도 덕분에 검색 엔진 순위를 올리는 데에도 도움이 된다.
25. 페이지의 HTML이 서버에서 미리 생성되기 때문에 크롤러가 페이지의 내용을 더 쉽게 읽을 수 있다.
SSR의 문제점
1. 무엇이든 보여주려면 모든 데이터를 fetch 해야 한다.
a. 현재의 SSR은 컴포넌트가 데이터를 기다리는 것을 허용하지 않는다.
b. HTML로 렌더링할 때 서버에서 컴포넌트의 모든 데이터가 준비되어야 한다.
c. 예를 들어, 댓글이 포함된 게시글을 렌더링한다고 가정해 보자
d. 댓글을 일찍 보여주는 것이 중요하므로 서버 HTML 출력에 포함시키고 싶다.
e. 그러나 데이터베이스나 API 계층이 느릴 수 있고, 이는 제어할 수 없는 부분이다.
f. 이제 어려운 선택을 해야한다.
g. 서버 출력에서 제외한다면 사용자는 JS가 로드될 때까지 댓글을 볼 수 없다.
h. 서버 출력에 포함시키면 댓글이 로드되고 전체 트리를 렌더링할 수 있을 때까지 나머지 HTML의 전송을 지연시켜야 한다.
2. 클라이언트에서 모든 컴포넌트의 Javascript를 로드하기 전에는 어떤 것도 hydration 할 수 없다.
a. React는 Javascript 코드가 로드되면 HTML을 hydration한다.
b. React는 서버에서 생성된 HTML을 보면서 컴포넌트를 렌더링하고 그 HTML에 이벤트 핸들러를 연결한다.
c. 이를 위해서 브라우저에서 생성된 컴포넌트 트리와 서버에서 생성된 트리가 일치해야 한다.
3. 모든 것을 hydration하기 전에는 어떤 것도 상호작용 할 수 없다.
a. React는 hydrate를 시작하면 전체 트리에 대해 이 작업을 완료할 때까지 멈추지 않는다.
b. 따라서 모든 컴포넌트가 hydration 되기 전까지는 그 어떤 것과도 상호작용할 수 없다.
c. 예를들어 댓글 부분이 복잡한 렌더링 로직을 가지고 있어 hydration이 오래 걸린다면 네비게이션바, 사이드바, 게시물 내용과 상호작용할 수 없다.
이 문제들을 어떻게 해결할 수 있을까?
● 서버에서 클라이언트로 HTML Streaming
1. 모든 데이터가 fetch되기 전에 완료된 부분부터 먼저 브라우저로 보내어 사용자에게 빠르게 표시될 수 있도록 한다.
2. 예를 들어 오래 걸릴 수 있는 댓글 부분을 Suspense로 감싸고 준비될 때 까지 React에게 <Spinner /> 컴포넌트를 표시하도록 지시할 수 있다.
<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
3. <Comments />를 Suspense로 감싸면 React에게 댓글을 기다리지 않고 페이지의 나머지부분에 대한 HTML Streaming을 시작하라고 알린다.
4. 그리고 댓글 대신 placeholder인 <Spinner />를 보낸다.
5. 초기 HTML을 보면 Comments 부분은 찾아볼 수 없다.
<main>
<nav>
<!--NavBar -->
<a href="/">Home</a>
</nav>
<aside>
<!-- Sidebar -->
<a href="/profile">Profile</a>
</aside>
<article>
<!-- Post -->
<p>Hello world</p>
</article>
<section id="comments-spinner">
<!-- Spinner -->
<img width=400 src="spinner.gif" alt="Loading..." />
</section>
</main>
6. 서버에서 댓글 데이터가 준비되면 React는 동일한 스트림에 추가 HTML을 보내고, 이 HTML을 올바른 위치에 넣기 위한 script 태그를 보낸다.
7. 결과적으로 클라이언트에서 React자체가 로드되기 전에도, 늦게 도착한 댓글의 HTML이 들어온다.
<div hidden id="comments">
<!-- Comments -->
<p>First comment</p>
<p>Second comment</p>
</div>
<script>
// This implementation is slightly simplified
document.getElementById('sections-spinner').replaceChildren(
document.getElementById('comments')
);
</script>
8. 전통적인 HTML 스트리밍과 달리, 이 작업은 위에서 아래로의 순서로 이루어질 필요가 없다.
9. 예를 들어, 사이드바에 데이터가 필요한 경우, Suspense로 감쌀 수 있고 React는 placeholder를 내보내고 게시물 렌더링을 계속할 것이다.
10. 그런 다음 사이드바 HTML이 준비되면, 이미 보낸 게시물의 HTML( 트리에서 더 아래에 있는)과 함께 React는 이를 Streaming하고 올바른 위치에 삽입하는 script 태그와 함께 보낸다.
11. 데이터가 특정 순서로 로드될 필요는 없다.
12. 우리는 스피너가 나타날 위치를 지정하고, 나머지는 React가 처리한다.
참고로 이 기능이 작동하려면 데이터 fetching solution은 Suspense와 통합되어야 한다. 서버 컴포넌트는 기본적으로 Suspense와 통합되어 있다.
● 모든 Js 코드가 로드 되기 전에 선택적으로 hydration 하기
1. React 18에서 Suspense는 댓글 위젯이 로드되기 전에 애플리케이션을 Hydration 할 수 있게 해준다.
2. 유저의 관점에서 최초에는 HTML로 Streaming된 상호작용할 수 없는 콘텐츠를 보게 된다.
3. Suspense안에 <Comments />를 감싸서 React가 페이지의 나머지 부분이 Streaming되는 것을 막지 않도록 한다.
4. 이제 React는 로드되는 동안 부분적으로 hydration 할 수 있게 된다.
● 모든 컴포넌트가 hydrate 하기 전에 완료된 부분과는 상호작용이 가능하다.
1. Suspense로 감싼다는 것은 hydration 과정이 브라우저가 다른 작업을 수행하는 것을 차단하지 않는다는 것이다.
2. 예를 들어, 사용자가 댓글이 hydration 되는 동안 사이드바를 클릭한다고 가정해 보자.
3. React 18에서는 Suspense 경계 내부의 컨텐츠가 hydration 될 때 브라우저가 이벤트를 처리할 수 있는 작은 간격이 생긴다.
4. 덕분에 클릭은 즉시 처리되며, 성능이 낮은 장치에서도 긴 hydration 동안 브라우저가 멈춘 것처럼 보이지 않는다.
5. 또한 hydration 중에도 사용자는 더 이상 관심이 없는 페이지에서 벗어날 수 있다.
6. 예제에서는 댓글만이 Suspense에 감싸져 있으므로 페이지의 나머지 부분은 한 번에 hydration 된다.
7. 하지만 우리는 필요에 의해 더 많은 곳에서 Suspense를 사용할 수 있다.
8. 예를 들어, 사이드바도 감싸보자.
<Layout>
<NavBar />
<Suspense fallback={<Spinner />}>
<Sidebar />
</Suspense>
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
9. 이제 둘 다 초기 HTML에 포함된 네비게이션 바와 게시물 이후에 서버에서 Streaming될 수 있다.
10. 둘 다 HTML이 로드되었지만, Js 코드는 아직 로드되지 않았다고 가정하자.
11. 이후에 사이드바와 댓글에 대한 코드를 포함하는 bundle이 로드된다.
12. React는 트리에서 먼저 찾은 Suspense 경계(이 예에서는 사이드바)부터 두 개를 hydration 하려고 시도할 것이다.
13. 하지만 사용자가 댓글 위젯과 상호작용을 시작한다고 가정해 보자.
14. React는 클릭 이벤트의 캡처 단계 동안 동기적으로 댓글을 hydration 한다.
15. 결과적으로, 댓글은 클릭을 처리하고 상호작용에 응답할 수 있도록 적시에 hydration된다.
16. 그리고 이제 React가 급하게 처리할 것이 없다면, React는 사이드바를 hydration 한다.
17. 선택적인 hydration 덕분에, "모든 것을 수화해야만 어떤 것과도 상호작용할 수 있다"는 필요가 없어진다.
18. React는 가능한 한 일찍 모든 것을 hydration하기 시작하며, 사용자 상호작용을 기반으로 화면의 가장 긴급한 부분을 우선시한다.
Suspense를 앱 전체에 적용할 경우 경계가 더 세분화될 때, 선택적 hydration의 이점이 더 분명해진다.
<Layout>
<NavBar />
<Suspense fallback={<BigSpinner />}>
<Suspense fallback={<SidebarGlimmer />}>
<Sidebar />
</Suspense>
<RightPane>
<Post />
<Suspense fallback={<CommentsGlimmer />}>
<Comments />
</Suspense>
</RightPane>
</Suspense>
</Layout>
1. 이 예제에서 사용자는 hydration이 시작될 때 첫 번째 댓글을 클릭한다.
2. React는 모든 부모 Suspense 경계의 콘텐츠를 우선적으로 hydration하되 관련 없는 형제 요소는 건너뛸 것이다.
3. 이는 상호작용 경로에 있는 컴포넌트가 먼저 hydration되어 hydration이 즉각적인 것처럼 보이는 환상을 낸다.
4. 그 후 React는 나머지 app을 바로 hydration 한다.
5. 정리하면 이 예제에서 초기 HTML은 <NavBar> 콘텐츠를 하고 나머지는 관련 코드가 로드되는 대로 부분적으로 Streaming되어 hydration되며, 사용자가 상호작용한 부분인 댓글을 우선적으로 처리한다.
'react' 카테고리의 다른 글
zustand에 devtools 사용하기 (0) | 2024.10.27 |
---|---|
useFormState hook으로 Form 상태를 다루자 (0) | 2024.03.10 |
shadcn/ui 를 활용하여 공통 컴포넌트를 쉽고 빠르게 ! (0) | 2024.03.05 |
nextjs 최신 버전 프로젝트 설정 (eslint, pretter) (0) | 2024.03.05 |
React 19 버전 canary (0) | 2024.03.05 |