Next.js 14 배포 시 캐시 문제 해결하기
KUKJIN LEE • 1개월 전 작성
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>
);
}