Next.js 동적 메타데이터로 인한 RSC Payload 크기 증가와 Content Download 최적화

2025년 12월 29일 월요일
1643글자
9분

0️⃣ 발단

팀 프로젝트 진행 후 QA를 진행하면서 제가 제작했던 목록 페이지에서 메뉴를 클릭하고 나서 할 일 목록 리스트가 표출될 때 까지의 시간이 뭔가 버벅인다는 느낌이 들어 원인 파악을 해봤습니다.

여러 방면으로 문제를 찾다가 확인하게 된 것이 RSC Payload 입니다.

RSC Payload는 Server Components의 렌더링 결과, Client components가 렌더링 되어야 할 위치와 해당 JavaScript 파일에 대한 참조, Server Components에서 Client Components로 전달된 모든 props를 React Server Components 트리의 간결한 이진 표현압니다.

RSC payload의 내용을 확인하고 싶다면 RSC Parser에서 확인할 수 있습니다.

개발자 도구를 통해 RSC Payload를 가져오는 시간을 측정해보니

content_download_before

Content Download 시간이 서버 응답을 기다린 시간과 거의 동일하거나 더 오래 걸리는 것을 확인하였습니다.
서버에서 응답은 이미 끝났지만 RSC Payload를 받아오는 시간이 길어져 목록이 느리게 표출된 것이었습니다.

왜 이렇게 Content Download의 시간이 늘어나게 됐는지 원인을 찾아보다가 Server Component로 실행되는 부분인 generateMetadata 함수에 작성한 로직이 원인이었습니다.

generateMetadata 함수는 SEO를 위해 생성할 메타데이터를 동적으로 생성하는 함수로, 서버 컴포넌트에서만 실행됩니다.

목록별 메타데이터와 브라우저 탭의 title을 변경하기 위해 해당 함수를 사용했는데, 병렬 라우팅으로 인해 두 개의 페이지에 대한 메타데이터를 생성하여 RSC Payload의 크기가 커졌고 이로 인해 Content Download 시간이 증가하게 되었습니다.

이 문제를 해결하기 위해 아래의 방법을 사용해보았습니다.

1️⃣ 동적 메타데이터를 정적 메타데이터로 변경

이 문제를 해결하기 위해 제일 먼저 생각하게된 방법은 간단하게 동적으로 생성하는 메타데이터를 정적으로 변경하는 방법입니다. g 기존 page.tsx 파일에 작성했던 동적 메타데이터를 layout.tsx 파일에 정적 메타데이터로 변경했습니다.

typescript
export const metadata: Metadata = {
  title: '할 일 목록',
  description: '할 일을 확인할 수 있습니다.',
};
content_download_after

변경 후에 Content Download 시간이 크게 줄어든 것을 확인할 수 있습니다.

하지만 이 방법은 선언되어있는 정보만 메타데이터로 만들어지기 때문에 각 페이지마다 메타데이터를 설정할 수 없습니다.

2️⃣ Next.js fetch 캐싱 사용하기

프로젝트를 진행할 때에는 위의 방법을 채택해서 최적화를 진행했지만, 이후에 코드를 다시 살펴보며 괜찮을 것 같다고 생각한 다른 방법들도 작성하겠습니다.

Next.js의 fetch 메서드는 cache 옵션을 통해 캐싱을 할 수 있습니다. 이 기능을 활용하면 메타데이터 생성을 위해 가져오는 정보들을 캐싱하여 목록 표출의 시간을 줄일 수 있을 것이라고 생각했습니다.

메타데이터를 생성하기 위한 두 개의 fetch 호출에 대해 cache 옵션을 적용한 후 확인해보았습니다.

typescript
const fetchTaskList = async (groupId: number, taskListId: number, token: string) => {
  try {
    const response = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/groups/${groupId}/task-lists/${taskListId}`,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${token}`,
        },
        cache: 'force-cache',
      },
    );

    return response.json();
  } catch (error) {
    throw error;
  }
};

첫 데이터 페칭 first_fetching

두 번째 페칭 second_fetching

이렇게 캐싱을 적용하면 첫 번째 데이터 페칭에서는 캐싱된 데이터가 없기 때문에 Content Download 시간이 좀 걸리지만, 그 이후의 페칭은 캐싱된 데이터를 사용하기 때문에 Content Download 시간이 줄어들었습니다.

결국 이 방법도 처음 한 번 데이터 페칭이 느리다는 단점이 존재합니다.

3️⃣ CSR로 변경

마지막으로 알아보게 된 방법입니다. 이는 제가 진행했던 프로젝트에서 참고한 Notion 웹 페이지를 개발자 도구로 meta 태그들을 확인해보았습니다.

notion_meta_tag

위처럼 기본으로 설정된 SEO를 그대로 사용하면서 title만 변경되는 모습을 확인할 수 있었습니다.
이걸 보면서 이전의 단점이라고 생각했던 업무마다 브라우저의 탭 제목이 변경되지 않는 문제, RSC Payload 다운로드로 인한 문제를 해결할 수 있겠다는 생각이 들어 적용해보기로 했습니다.

업무 목록 페이지에서는 "업무 목록"이라는 탭의 타이틀로 표출하고, 상세 페이지를 표출한 상태에서는 해당 업무의 제목으로 설정하겠습니다.

상세 페이지를 표출하는 컴포넌트에 useEffect() 로직을 추가해주었습니다.

typescript
// TaskDetail.tsx
const { data: taskDetailData, isPending } = useGetTaskDetail(groupId, taskListId, taskId);

useEffect(() => {
  if (isPending) return;

  document.title = `${taskDetailData?.name} | Coworkers` || '업무 목록 | Coworkers';
}, [taskDetailData, isPending]);

이렇게 작성하면 데이터 페칭이 끝난 후 탭의 타이틀을 업무 이름으로 변경하게 됩니다.

change_title_01

변경하면서 노션 페이지와는 약간 다른 점이 있습니다.

노션은 메타데이터가 변경되지 않지만, 제 프로젝트에서는 모든 메타태그가 다시 만들어지는 모습을 확인할 수 있습니다.
Next.js는 페이지 경로가 바뀌면 정적 메타데이터이더라도 해당 경로에 맞는 메타데이터로 <head>를 동기화 하기 때문입니다.

또한 이미 선언해 둔 메타데이터로 인해 title이 덮어씌워질 수 있는 문제가 발생합니다. 이를 해결하기 위해 Layout.tsx의 <html> 태그 내부에 메타태그를 선언하면 title 부분만 수정되는 것을 확인할 수 있습니다.

add_meta_tag

이렇게 메타데이터 설정과 페이지에 따라 브라우저 title을 변경하는 법에 대해 알아보고, Next.js에서 정적, 동적 메타데이터에 대해 알아보는 시간을 가져보았습니다.

게시글 제목:Next.js 동적 메타데이터로 인한 RSC Payload 크기 증가와 Content Download 최적화

작성자:huui

게시글 링크:https://huuitae.github.io/posts/fetch_metadata_content_download_loading [복사]

마지막 수정일:


상업적 복제의 경우, 웹마스터에게 허가를 요청하십시오. 비상업적 복제의 경우, 본 기사의 출처와 링크를 명시해 주십시오. 본 저작물은 어떤 형태로든, 어떤 매체로든 자유롭게 복제 및 배포할 수 있으며, 수정 및 제작도 가능합니다. 단, 2차 저작물을 배포할 경우에도 동일한 라이선스 계약을 적용해야 합니다.
이 게시글은 다음을 채택합니다.CC BY-NC-SA 4.0의 허가를 받아야 합니다.