캐싱으로 Next.js 앱 속도 높이는 방법
Source: Dev.to
위에 제공된 소스 링크만 포함되어 있어 번역할 본문이 없습니다. 번역을 원하는 전체 텍스트를 알려주시면 한국어로 번역해 드리겠습니다.
캐싱이란?
Caching은 기본적으로 이미 가져오거나 계산한 데이터를 기억하는 것을 의미하며, 따라서 같은 작업을 다시 할 필요가 없습니다.
- 사용자가 페이지를 방문할 때마다 API를 호출하거나 데이터베이스에 접근하는 대신, 앱이 해당 데이터를 일시적으로 저장하고 업데이트가 필요할 때까지 재사용할 수 있습니다.
- 이것을 뇌가 집 주소를 기억하는 것에 비유할 수 있습니다. 양식을 작성할 때마다 주소를 찾아볼 필요가 없죠. 같은 개념이 앱에도 적용됩니다.
캐싱은 어디에서 발생할 수 있나요?
React와 Next.js에서는 캐싱이 다양한 레벨에서 발생할 수 있습니다:
| 계층 | 캐시할 수 있는 항목 |
|---|---|
| 클라이언트 측 | API 응답, 계산된 값, 컴포넌트 출력 |
| 서버 측 | SSR/SSG 중에 가져온 데이터, 라우트 수준 데이터 |
| 네트워크 / CDN | 전체 페이지, 정적 자산, 엣지 캐시된 API 라우트 |
각 계층을 현명하게 활용하면 앱을 더 빠르게 만들 수 있는 기회를 제공합니다.
왜 캐시가 필요할까?
- 사용자에게 페이지 로드 속도를 높여줍니다.
- 서버 비용 및 API 사용량을 줄여줍니다.
- 앱이 더 부드럽고 반응성이 좋아집니다.
- 페이지 로드 시간을 단축해 SEO와 Core Web Vitals를 개선합니다.
Next.js 캐싱 기능
1. fetch()와 내장 캐싱 제어
// Example: cache for 1 hour
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // seconds
});
revalidate는 지정된 초가 지난 후 Next.js가 데이터를 다시 가져오도록 합니다.- 자주 변경되지 않는 데이터(블로그 포스트, 제품 목록, 정적 콘텐츠)에 적합합니다.
항상 최신 데이터를 받아야 할 경우:
const data = await fetch('/api/always-fresh', {
cache: 'no-store' // never cache
});
2. 캐시 컴포넌트 ('use cache' 지시문)
// Sidebar component – cached
async function Sidebar() {
'use cache';
const categories = await fetch('/api/categories');
return (
<>
{/* Sidebar content here */}
</>
);
}
- 서버 컴포넌트 내부에
'use cache'를 사용하면 UI 그 부분만 캐시됩니다. - 사이드바, 네비게이션 등 거의 변하지 않는 UI에 좋습니다.
3. 라우트 / 페이지 세그먼트 캐싱 (SSG & ISR)
// pages/blog/[slug].tsx
export const revalidate = 3600; // 1 hour
- Next.js는 빌드 시 페이지를 사전 렌더링하고, 지정된 간격이 지나면 백그라운드에서 재검증합니다.
- 블로그 글, 제품 페이지 등 정적 속도와 동적 최신성을 동시에 원하는 경우에 이상적입니다.
React‑Side 캐싱 기법
1. 메모이제이션 (useMemo & useCallback)
import { useMemo } from 'react';
const sortedList = useMemo(() => {
return data.sort((a, b) => a.name.localeCompare(b.name));
}, [data]); // recompute only when `data` changes
- 비싼 계산(예: 정렬)이 매 렌더링마다 실행되는 것을 방지합니다.
2. 내장 캐시가 있는 데이터 가져오기 라이브러리
SWR 예시
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(res => res.json());
export default function Profile() {
const { data, error } = useSWR('/api/user', fetcher, {
refreshInterval: 60000 // revalidate every minute
});
if (error) return <div>Error loading</div>;
if (!data) return <div>Loading...</div>;
return <div>Hello {data.name}</div>;
}
- SWR은 응답을 캐시하고, 이후 렌더링에서는 즉시 반환하며, 백그라운드에서 재검증합니다.
- React Query도 동일하게 동작하므로, 스타일에 맞는 것을 선택하면 됩니다.
간단한 캐싱 체크리스트
- Identify 자주 변경되지 않는 데이터를 식별합니다.
- user‑specific 또는 time‑sensitive 데이터를 동적으로 유지합니다.
- Cache only where it improves performance.
- Set a revalidation time 또는 모든 캐시 항목에 대한 만료 시간을 설정합니다.
- Test & monitor 결과를 테스트하고 모니터링합니다 (예: Lighthouse, 서버 로그).
캐싱은 모두를 캐시하는 것이 아니라, 올바른 것을 캐시하는 것입니다.
일반적인 캐싱 실수
| 실수 | 왜 문제가 되는가 |
|---|---|
| 과도한 캐싱 – 만료 시간을 설정하지 않음 | 오래된 데이터를 제공함 |
| 사용자 데이터를 전역으로 캐싱 | 세션이 뒤섞이고 개인 정보가 유출됨 |
| 무효화(Invalidation)를 잊음 | 업데이트가 절대 표시되지 않음 |
| 디버깅 혼란 – 데이터가 캐시되어 있다고 가정하고 최신이라고 생각함 | “누락된” 업데이트를 찾는 데 시간 낭비 |
황금 규칙: 속도와 신선도의 균형.
실제 사례 균형 (내 예시)
- Sidebar categories & featured posts –
'use cache'로 캐시됨. - Post list –
fetch(..., { next: { revalidate: 600 } })로 가져옴 (10분마다 재검증). - User‑specific sections (comments, notifications) – 항상 동적(캐시 안 함).
이는 정확성을 손상시키지 않으면서 빠른 사이트를 제공합니다.
TL;DR
- Caching = 데이터를 기억해서 작업을 다시 하지 않게 함.
- Next.js의
fetch옵션, Cache Components, ISR을 사용해 서버‑사이드 캐싱을 수행합니다. - React의
useMemo,useCallback및 SWR 혹은 React Query 같은 라이브러리를 사용해 클라이언트‑사이드 캐싱을 수행합니다. - 정적인 것은 캐시하고, 동적인 것은 최신 상태로 유지하며, 항상 재검증 전략을 설정합니다.
캐싱은 앱을 즉각적으로 느끼게 하는 가장 쉬운 방법 중 하나입니다.
즐거운 코딩 되세요! 🚀
React 또는 Next.js 앱을 번개처럼 빠르게 만들기
전체 시스템을 재구축할 필요 없이 영향을 확인할 수 있습니다 — 작은 것부터 시작하세요. 자주 변경되지 않는 부분을 캐시하고, 테스트한 뒤 점차 확장해 나가세요.
Next.js가 빠르게 진화함에 따라 route caching, fetch‑level caching, cache components와 같은 기능이 이전보다 성능 최적화를 훨씬 간단하게 만들어 줍니다.
캐싱을 현명하게 활용하면 앱이 더 빨리 로드될 뿐만 아니라 적은 노력으로 더 많은 사용자를 처리할 수 있습니다.
읽어 주셔서 감사합니다. 이런 콘텐츠를 더 보고 싶다면, React, Next.js, 그리고 웹 성능에 대한 실용적인 인사이트를 공유하는 제 다른 블로그들을 확인해 보세요.