왜 React는 Server Component를 만들게 되었을까?
Purpose
React 18이 2022년에 출시되었고, Nextjs App router가 2023년에 출시되어 이미 케케묵은 조사이지만, nextjs page router와 app router를 쓰며 어떤 점이 달라졌고, 왜 달라졌는지 이유를 찾던 중 app router를 왜 사용하게 되었는지 이유를 찾게 된 글이 있다. 그 글을 읽고 이해한 바를 정리하고자 한다.
Story
글을 읽기 전에 다음과 같은 상황을 가정해보자.
새로운 회사로 이직을 하기 위해 Frontend 개발자 면접자리에서 다음과 같은 질문을 받았다고 하자.
아니면, 새로운 프로젝트 혹은 기존 프로젝트에서 nextjs app router를 사용하자고 팀원들을 설득을 하는 자리이거나,
혼자서 nextjs의 page router와 app router 중 어떤 기술이 적합한지 고민하고 있는 상황이라고 하자.
단순 app router가 새롭게 나온 기술이고, Frontend 개발자로써 app router를 사용해보자는 답안도 충분히 설득력이 있을 수 있다. 하지만, 아래와 같은 질문의 답을 이해하고 있다면, 조금 더 설득력 있는 답변을 할 수 있을 것이다.
- Q. SSR(Server Side Rendering)과 CSR(Client Side Rendering)의 차이점은 무엇인가?
- Q. 기존 방식 (nextjs page router)에서 어떤 한계가 nextjs app router 방식을 만들게 되었을까?
- Q. React에서
Suspense
를 사용하는 이유는 무엇일까?
위 질문의 답을 알고 있다면, 본문을 읽지 않아도 무방할 것이고 조금 더 신뢰도가 있는 글을 원한다면 React 18 Architecture 소개 글을 보아도 좋을 것이다. 본문을 읽지 않고 답만 알고 싶다면 Result를 먼저 살펴보자.
다음 본문은 Architecture 글을 읽고, 이해한 바를 필자의 의견을 섞어 정리한 글이다.
Server Side Rendering(SSR) 이란?
먼저 Server Side Rendering(SSR)이 무엇인지 구분 할 필요가 있다. SSR 이전에 비교를 하게 되는건 Client Side Rendering(CSR)인데, 그렇다면 먼저 왜 SSR이 필요하게 된 걸까?
CSR의 동작 방식은 다음과 같다.
- 사용자는 HTML을 받아온다.
- Browser는 HTML에 포함되어 있는 JS를 다운로드 한다.
- JS를 다운로드 받은 브라우저는 JS를 실행하고, 화면을 그린다.
- 사용자는 완성 된 페이지를 보고 상호작용을 할 수 있다.
CSR의 방식에서 개선이 필요하다고 생각하는 건 1번에서 3번이 끝나기 전까지 사용자는 빈 화면을 보게 된다는 점이다. 빈 화면이 표시되기 전 미리 HTML에 로딩 화면이나, splash 이미지를 넣어주곤 했지만 workaround 일 뿐 유저가 원하는 화면을 보지 못한다는건 동일하다.
이러한 한계를 극복하기 위해 사용되는게 SSR이다. SSR은 서버에서 미리 HTML을 만들어서 사용자에게 전달하는 방식이다. SSR의 동작 방식은 다음과 같다.
- 사용자가 HTML을 요청하면, 서버에서 HTML을 만들어서 사용자에게 전달한다.
- 사용자는 완성 된 화면을 볼 수 있고, Browser는 HTML에 포함 된 JS를 다운로드 한다.
- JS를 다운로드 받은 브라우저는 JS를 실행하고, 화면에 있는 요소에 상호작용을 할 수 있도록 이벤트를 붙인다(hydration).
- 사용자는 완성 된 페이지에서 상호작용을 할 수 있다.
SSR의 방식은 1번에서 이미 완성된 화면을 볼 수 있다는 점이다. 기존 CSR은 JS를 로드하고 화면을 그리기까지 화면을 볼 수 없던 반면, SSR은 미리 서버에서 완성된 화면을 보여주기 떄문에 처음부터 화면을 볼 수 있다. 3번의 hydration을 진행하기 전까지는 유저의 상호작용(클릭, 텍스트 입력 등)이 불가능하지만 화면을 볼 수는 있기 떄문에 사용자 경험을 향상 시킬 수 있다.
정리하면, 유저에게 화면을 보여주기 위해서 JS를 로드하는 한계를 극복하기 위해 서버에서 HTML을 그려주어서 사용자에게 전달하는 방식이 SSR이다.
그렇다면 이 방식은 어떤 한계가 있어서 새로운 SSR architecture를 만들게 되었을까?
기존 SSR의 한계는?
새로운 설계가 나오기 위해서는 기존 설계의 한계가 있어야 한다. 그 한계를 알고 있다면, 새로운 설계의 원리 및 의도하는 바를 이해할 수 있으므로, 먼저 어떤 부분이 한계로 동작했는지 알아보자.
여기서 이야기하는 기존 SSR은 정확히 nextjs page router의 특징이기도 하다.
// pages/product/[id].js
// Client / Server both run
const Product = ({ product }) => {
return (
<div>
<h1>Product: {product.name}</h1>
<p>Price: ${product.price}</p>
</div>
);
};
// Server Only Part
export async function getServerSideProps(context) {
const { id } = context.params;
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
return {
props: { product },
};
}
export default Product;
위 코드는 nextjs의 page router에서 사용되는 코드이다. Nextjs의 page router에 익숙하다면 기존 SSR 로직을 이해하기 쉬울 텐데,
기존 CSR과 SSR의 차이점이 발생하는 점이 getServerSideProps
인데, 해당 함수는 Server에서만 동작한다.
그리곤, Server에서 받아온 데이터를 Product
컴포넌트로 props로 넘겨주고, Product의 HTML을 server에서 만들어서 사용자에게 전달한다.
이런 방식에서 한계가 드러나는데, "getServerSideProps
는 page 단위로만 사용 가능하다." 라는 점이다.
server 에서 이루어지는 작업이 page 단위로만 가능하기 때문에, 기존 SSR의 흐름은 page 단위로 통째로 이루어진다는 점이다.
HTML 생성
HTML 생성시에도 모두 한번에 이루어져야한다. 화면을 그리기 위한 데이터가 모두 준비되어야 화면을 그리기 시작할 수 있고, 여러 데이터 중 하나의 데이터만 준비가 늦어진다면 전체 화면 로드가 늦어진다.
즉, 화면을 그리는데 아래 3가지 데이터를 필요로 하고 각각 데이터를 받아오는데 딜레이가 다르다고 가정하자.
- Article API (100ms ~ 300ms)
- Comment API (200ms ~ 1000ms)
- User API (300ms ~ 500ms)
이러한 경우 3종류의 API를 동시에 호출한다고 해도 가장 늦게 끝나는 Comment API를 기다려야하고, 최장 1초의 딜레이를 감수해야한다.
JS 로드
JS 로드시에도 마찬가지로 모두 한번에 이루어져야한다. 화 면의 특정 영역이 많은 JS 로직을 가지고 있다면, 해당 영역 JS를 로드한다고 다른 영역 모두 hydration을 진행하지 못한다.
- 기사 본문 (100KB)
- 댓글 (200KB)
- 사용자 정보 (50KB)
이러한 경우 기사 본문, 사용자 정보의 JS 리소스가 적어서 먼저 로드되더라도 댓글의 JS 리소스를 모두 받아오기까지 기다려야한다. 왜냐하면 hydration은 한번에 진행되는 설계로 이루어져있기 때문이다.
만약, 댓글로 인한 hydration 딜레이를 줄인다면 댓글 영역을 Server 영역에서 제외하는 방법이 있지만 그렇다면 댓글 영역은 CSR로 동작하게 되어서, 사용자는 CSR의 한계인 댓글 영역을 빈 화면(혹은 로딩)으로 보게되는 현상으로 돌아가게 된다.
Hydration
JS 로드와 비슷하게, hydration 이 한번에 이루어지므로 JS 로드 뿐 아니라 hydration도 한번에 이루어져야한다. 각 영역의 hydration이 이루어지는 시간이 아래와 같다고 가정하자. (hydration 시간은 추상적이다.)
- 기사 본문 (10ms)
- 댓글 (50ms)
- 사용자 정보 (20ms)
각각을 따로 진행한다면 이미 기사 본문과 사용자 정보를 상호작용 할 수 있는 상태여야하지만, 댓글쪽 hydration이 끝나기 전이므로 사용자는 모든 영역의 상호작용을 할 수 없다. 댓글이 아니라 유저 정보를 클릭하고 싶어도 페이지의 모든 영역이 끝날 떄까지 기다려야하는 것이다.