원티드 프리온보딩 챌린지 "Next.js 확실히 알고 레벨업 하기" 2일차 내용 정리
리액트 18 이전 버전 SSR의 문제
- 데이터 페칭(모두 완료되어야 html을 만들 수 있음) -> html로 렌더링 -> js 파일 로드 -> hydration 과정이 필요하다.
- Hydration(수화) : 척박한 html에 물을 뿌려주어야 사용자와 상호작용을 할 수 있다.
- 여러가지 컴포넌트가 한 페이지에 표시될 때, 한 컴포넌트가 무거워서 js 로드에 오랜 시간이 걸린다면 다른 컴포넌트들도 완료되기 전까지 Hydration을 할 수가 없다. 즉 모든 js가 로드되기 전에는 페이지에서 아무런 상호작용을 할 수 없음.
// 모든 fetch가 끝나야 Hydration을 시작한다.
function getServerSideProps () => {
const info1 = fetch("/...");
const info2 = fetch("/...");
const info3 = fetch("/...");
Promise.all(info1, info2, info3);
return {props: {
info1: info1,
info2: info2,
info3: info3
}};
}
리액트 18이 해결한 방법
- Streaming HTML + Suspense 사용함.
- 이전 버전에서는 페이지 단위로 모든 컴포넌트를 한번에 진행했으나, 18버전부터는 컴포넌트마다 개별적으로 위의 과정을 수행하는 것으로 변경됨. (동시성)
※ 참고: 자바스크립트의 동시성에 대해
- 자바스크립트는 콜스택에 작업들을 넣고 순서대로 하나씩 처리하는 싱글 스레드 언어지만, 콜백을 사용한다면 비동기/논블로킹으로 비동기 작업들을 동시에 진행한다.
- 상세과정 : WebAPI에서 병렬로 비동기 작업을 수행한 뒤, 완료되면 태스크 큐에 콜백을 넣는다. 그리고 이벤트 루프는 콜스택이 비어있다면 태스크 큐에 있는 콜백을 콜스택에 넣는다. 0초가 걸리는 setTimeout이라도 무조건 콜스택에 곧바로 들어간 작업(=동기)보다 늦게 결과를 뱉는데, 이것 때문이다.
React Server Component(RSC)
- RSC란 서버에서 실행되는 React 컴포넌트이다.
- Next.js 서버에서 RSC를 실행하면서 데이터 페칭 및 렌더링을 진행한다.
- 렌더링 완료 후 생성된 HTML을 클라이언트로 전달한다.
- 클라이언트는 Hydrate 과정 없이 HTML을 그대로 사용한다.
- 장점 : 번들 파일로 만들어지지조차 않는다. 그래서 js 용량이 감소하고 성능이 높아진다.
- 단점 : useState, useEffect, window 등 client 기능을 사용할 수 없다. 왜? js가 없기 때문. 쿠키 값을 받을 수는 있음.
- 그래서 사용자 상호작용이 필요 없이 서버에서 데이터만 가져와서 보여주면 되는 부분은 RSC로 만들고, 상호작용이 필요하다면 클라이언트 컴포넌트를 사용한다. (RSC 안에서 클라이언트 컴포넌트 사용가능. 반대는 오류) - 아래 코드 참고
// LikeButton.tsx
"use client";
import { useState } from "react";
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button
className={`px-3 py-1 rounded ${liked ? "bg-red-500 text-white" : "bg-gray-200"}`}
onClick={() => setLiked(!liked)}
>
{liked ? "Liked" : "Like"}
</button>
);
}
// PostListWithLike.tsx
async function getPosts() {
const res = await fetch("...");
return res.json();
}
export default async function PostListWithLike() {
const posts = await getPosts();
return (
<ul>
{posts.slice(0, 5).map((post: any) => (
<li key={post.id} className="p-2 border-b flex justify-between items-center">
<div>
<h2 className="text-lg font-bold">{post.title}</h2>
<p>{post.body}</p>
</div>
<LikeButton />
</li>
))}
</ul>
);
}
직렬화
- JSON.stringify()와 같은 것.
- React만의 직렬화 방식을 사용한다. xml 형식 -> key-value 형식으로 직렬화.
- 함수는 직렬화할 수 없다. 실행 컨텍스트(현재 실행되는 코드의 환경까지 담고 있는 복잡한 구조체)까지 직렬화할 수 없기 때문.
SSR의 장점
상호작용 할 수 없는 HTML을 빨리 보여줘서 UX를 개선
SSR + RSC
- RSC는 SSR 방식의 렌더링을 서버에서 처리하게끔 해줌
- 도메인으로 접속하여 API로 요청을 하고 응답이 오면 렌더링된 html을 전달받으므로 정적인 사이트가 표시된다.
- (RSC가 아닌 경우) 그 다음, js를 다운로드하고 Hydrate 과정을 거친다. (RSC는 js가 없기 때문)
- Hydrate 과정이 끝나면 상호작용이 가능하고, 여기까지 왔다면 렌더가 완료된 상태이다.
- 그런데 RSC에서 API를 여러개 요청하는 경우에는 어떨까? 이미 HTML을 내려받았는데 두번째 API의 결과는 어떻게 처리할까? -> 그것을 가능하게 해 주는 것이 Streaming HTML.
- 컴포넌트 각각 개별적인 실행을 원한다면 Suspense까지 사용해야 함. (예시는 아래 코드 참고)
async function RscMainContent(): Promise<Element> {
const fetching = new Promise(...);
}
export default function Page(): Element {
return (
<main>
<Suspense fallback={<div>loading</div>}>
<RscMainContent />
</Suspense>
<Suspense fallback={<div>loading</div>}>
<RSCSubContent />
</Suspense>
</main>
);
}
서버라는 개념이 들어가서 헷갈리는 부분이니까 정확하게 알아야 코딩할 때 의아하지 않을 수 있다!
'IT > React & Next.js' 카테고리의 다른 글
[React] 커스텀 훅으로 로직 분리 (0) | 2025.02.14 |
---|---|
[React] 불필요한 useEffect와 State 제거하기 (0) | 2025.02.13 |
[JavaScript/React] 다른 탭 간에 데이터 공유하기(postMessage) (0) | 2025.02.09 |
[Next.js] Next.js에 대해 (0) | 2025.02.08 |
[IT] FE 레거시 코드 리팩토링 강의 정리 2 (feat. 최적화) (0) | 2025.01.18 |