서버 컴포넌트 캐싱과 최적화
해당 서버 컴포넌트를 RSC payload로 변환하는 과정을 캐싱을 통해 최적화 하는 것임.
- 사용자 요청이 들어오면 빌드 시 도출한 module graph를 바탕으로
- RSC payload를 만들고
- SSR을 통해 초기 렌더링 시 보여줄 html을 만들게 됨.
여기서 서버 컴포넌트 캐싱이란,
RSC payload로 변환하는 과정을 캐싱을 통해 최적화 하는 것임.
즉, 전체 RSC payload를 만드는 과정에서, 캐싱 설정한 서버 컴포넌트에 대해서는 별도의 추가 과정 없이 캐싱된 JSON 블럭을 바로 삽입 시킴으로써 캐싱의 이점을 활용하는 방식.
따라서 SSR을 통해 초기 html을 생성하는 과정은 여전히 거쳐야함.
html로 변환하지 않고 module graph 형태로 갖고 있는 이유는
RSC 생성과정 및 SSR 이해 에서 자세히 다루었음.
활용 예시
SNS 게시물
- 사용자별로 보여줘야 하는 맞춤 게시물 목록이 다 다름.
- 따라서 사용자별 게시물 화면은 SSR을 통할 수밖에 없음.
- 하지만 게시물 자체에 대한 내용은 변하지 않음.
- 따라서 각 게시물을 캐싱 처리.
- 각 사용자의 맞춤 피드 목록만 동적으로 생성하는 방식으로 최적화
- 이 과정에서 각 게시물들은 캐싱된 것을 활용해 RSC Payload를 빠르게 생성 및 변환 비용 감소.
- 즉 캐싱된 각 게시물들을 조합하여 구성하는 정도로 사용자 맘춤 피드목록 구성 행위를 최적화 할 수 있음.
모든 게시물에 대해 캐싱을 하는 것 자체로도 엄청난 부하가 아닐까? 싶을 수도 있지만,
이걸 매번 변환시키는 과정이 훨씬 더 큰 부하임.
따라서 캐싱이 더 효율적.
const Lesson = cache(async function Lesson({ lessonId }: { lessonId: string }) { const lesson = await fetch(`https://api.example.com/lesson/${lessonId}`, { next: { revalidate: 600 } }).then(res => res.json()); return ( <div> <h2>{lesson.title}</h2> <p>{lesson.description}</p> </div> ); }); export default Lesson;
이점
- 변환 비용 감소:
- 매번 변환하거나 렌더링하는 것은 CPU 리소스를 많이 사용하고, 특히 다양한 사용자에게 맞춤형 피드를 동적으로 생성하는 경우 시간이 많이 소요된다.
- 반면, 게시물 콘텐츠는 한번 렌더링 후 캐시해두면, 다시 변환할 필요 없이 빠르게 제공할 수 있다. 이 과정을 통해 리소스 소모가 줄어들고 응답 시간도 단축된다.
- 빠른 응답 시간:
- 캐시된 데이터를 사용하면, 서버에서 새로 렌더링하지 않고 이미 계산된 데이터를 빠르게 반환할 수 있다.
- 사용자에게 빠른 응답을 제공하는 것이 매우 중요한 대규모 서비스에서는 캐시를 적극적으로 활용하여 사용자 경험을 향상시킨다.
- 서버 부하 분산:
- 캐시된 게시물은 서버 부하를 분산시킬 수 있습니다. 매번 서버가 전체 데이터를 계산하고 변환하는 대신, 캐시된 결과를 바로 반환할 수 있기 때문에, 서버의 자원 소모가 크게 줄어든다.
- 특히 많은 사용자가 같은 게시물을 조회하는 경우, 캐시를 통해 서버 자원을 효율적으로 분배할 수 있다.
나아가
서버 컴포넌트 캐싱 뿐만 아니라, 요청 자체에 대한 캐싱도 중요하다.
사용자별로 보여줘야 하는 화면이 달라서 SSR을 통해야 할 지라도, 동일한 요청에 대해서 캐싱을 불필요한 반복 연산을 줄일 수 있다.
결국엔 서버 컴포넌트를 캐싱 하더라도, module graph 형태로 지니고 있기 때문에 이를 RSC Payload로 변환하는 과정은 매번 수행되어야 한다.
게시글 등이 다 캐싱 되어 있다고 하더라도, 이걸 조합&구성 하는 코스트는 매번 발생하는 것이다.
피드를 구성하는 조합 과정도 캐싱하여 불필요한 연산과 응답 속도에 대해 최적화 할 수 있다.
import { cache } from "react"; const UserLessonList = cache(async function UserLessons({ userId }: { userId: string }) { const lessons = await fetch(`https://api.example.com/recommended-lessons/${userId}`, { next: { revalidate: 300 } }).then(res => res.json()); return ( <div> {lessons.map((lesson) => ( <Lesson key={lesson.id} lessonId={lesson.id} /> ))} </div> ); }); export default UserLessons;