Next.js 14 배포 시 캐시 문제 해결하기

KUKJIN LEE's profile picture

KUKJIN LEE1개월 전 작성

Next.js 14의 App Router는 기본적으로 여러 레이어에서 캐싱을 수행합니다.

※ Next.js 15버전을 사용합시다!!

  • 캐싱이란?

    • 프론트엔드: 브라우저에서 CSS, JS 파일을 캐시하여 페이지 로딩 속도를 향상.

    • 백엔드: 서버에서 자주 조회되는 데이터를 메모리에 캐시하여 데이터베이스 접근을 줄임.

 

일반적인 캐시 관련 문제

  • 동적 데이터의 정적 캐싱: 자주 변경되는 데이터가 예기치 않게 캐시되는 문제

  • 부분 라우트 무효화 실패: 특정 세그먼트만 업데이트해야 할 때 발생하는 문제

  • 서버 컴포넌트와 클라이언트 컴포넌트 간 캐시 불일치: 서버와 클라이언트의 상태가 일치하지 않는 문제

  • ISR(Incremental Static Regeneration)과 온디맨드 리밸리데이션 충돌: 캐시 갱신 전략 간 충돌

 

동적 데이터 문제 해결

cache: 'no-store' 옵션을 사용하여 동적 데이터를 매 요청마다 새로 가져올 수 있습니다.

// app/dynamic-data/page.js
export default async function DynamicDataPage() {
  const data = await fetch('https://api.example.com/data', { cache: 'no-store' });
  const jsonData = await data.json();

  return <div>{JSON.stringify(jsonData)}</div>;
}
 

부분 라우트 문제 해결

revalidatePath 함수를 사용하여 특정 경로의 캐시를 무효화할 수 있습니다.

// app/api/revalidate/route.js
import { revalidatePath } from 'next/cache';

export async function GET(request) {
  const path = request.nextUrl.searchParams.get('path');
  revalidatePath(path);
  return Response.json({ revalidated: true, now: Date.now() });
}

 

서버 및 클라이언트 컴포넌트 문제 해결

unstable_noStore를 사용하여 서버 컴포넌트의 캐싱을 비활성화하고, 데이터를 클라이언트 컴포넌트에 전달합니다.

// app/sync-component/page.js
import { unstable_noStore as noStore } from 'next/cache';
import ClientComponent from './ClientComponent';

export default async function SyncPage() {
  noStore();
  const data = await fetch('https://api.example.com/data');
  const jsonData = await data.json();

  return <ClientComponent serverData={jsonData} />;
}

 

ISR과 온디맨드 리벨리데이션(On-demand Revalidation) 문제 해결

  • ISR이란?

    • 정적 사이트 생성을 확장하여, 정적 페이지 빌드 후 주기적으로 재생성할 수 있게 하는 기능, 주기적으로 업데이트된 데이터를 반영할 수 있다.

  • 온디맨드 리벨리데이션(On-demand Revalidation)

    • 데이터 업데이트, 컨텐츠 변경이 발생할 때 정적 페이지를 즉시 재생성하는 기능

// app/isr-page/page.js
export const revalidate = 60; // 60초마다 ISR

export default async function Page() {
  const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });
  const data = await res.json();
  return <div>{JSON.stringify(data)}</div>;
}

// app/api/revalidate/route.js
import { revalidatePath } from 'next/cache';

export async function GET(request) {
  const path = request.nextUrl.searchParams.get('path');
  revalidatePath(path);
  return Response.json({ revalidated: true, now: Date.now() });
}

ISR을 사용하면서도 필요시 온디맨드로 리밸리데이션할 수 있는 API 엔드포인트를 별도로 구현합니다.

 

캐시 최적화 기법

  • Suspense 도입
    • 큰 페이지를 작은 단위로 나눠 로드하여 사용자 경험 개선
// app/streaming-page/page.js
import { Suspense } from 'react';
import SlowComponent from './SlowComponent';

export default function StreamingPage() {
  return (
    <div>
      <h1>Fast Content</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}
  • 병렬 데이터 Fetching

    • 여러 데이터를 병렬로 처리하여 전체 로딩 시간을 단축

// app/parallel-fetch/page.js
export default async function ParallelFetchPage() {
  const dataPromise1 = fetch('https://api.example.com/data1');
  const dataPromise2 = fetch('https://api.example.com/data2');

  const [data1, data2] = await Promise.all([dataPromise1, dataPromise2]);
  const [jsonData1, jsonData2] = await Promise.all([data1.json(), data2.json()]);

  return (
    <div>
      <div>{JSON.stringify(jsonData1)}</div>
      <div>{JSON.stringify(jsonData2)}</div>
    </div>
  );
}

New Tech Posts