6.0 KiB
6.0 KiB
7. 최적화 캐시 / 재호출 전략
캐시 레이어 구조
Nuxt의 캐시는 여러 레이어에서 작동한다.
브라우저 캐시 (HTTP Cache-Control)
↑
CDN / Edge 캐시
↑
Nitro 서버 캐시 (cachedEventHandler, cachedFunction)
↑
useFetch / useAsyncData 캐시 (payload 기반)
1. useFetch / useAsyncData 캐시
기본 캐시 동작 (payload)
<script setup>
// 서버에서 가져온 데이터는 payload에 저장됨
// 클라이언트에서 같은 키로 요청하면 재요청 없이 payload 사용
const { data } = await useFetch('/api/products', {
key: 'products-list' // 명시적 캐시 키 (기본값: URL + params 조합)
})
</script>
stale-while-revalidate 패턴
"캐시된 데이터를 즉시 보여주면서 백그라운드에서 최신 데이터를 가져오는" 전략이다.
<script setup>
const { data, refresh } = await useFetch('/api/products', {
// 1. 캐시된 데이터로 즉시 렌더링
// 2. 백그라운드에서 새 데이터 요청
// 3. 새 데이터 도착 시 자동으로 UI 업데이트
getCachedData(key, nuxtApp) {
// nuxtApp.payload.data에 이미 캐시된 데이터가 있으면 사용
return nuxtApp.payload.data[key] ?? nuxtApp.static.data[key]
}
})
</script>
수동 refresh와 자동 watch
<script setup>
const page = ref(1)
const category = ref('tent')
const { data, pending, refresh } = await useFetch('/api/products', {
query: { page, category },
watch: [page, category], // 값이 바뀌면 자동으로 재요청
})
// 수동으로 재요청
async function handleRefresh() {
await refresh()
}
</script>
invalidation — 캐시 무효화 타이밍
<script setup>
const { data: products, refresh } = await useFetch('/api/products')
// 상품 등록 후 목록 갱신
async function createProduct(newProduct) {
await $fetch('/api/products', {
method: 'POST',
body: newProduct
})
// 캐시 무효화: 수동 refresh
await refresh()
}
// 또는 clearNuxtData로 특정 키 캐시 삭제
function invalidateCache() {
clearNuxtData('products-list')
}
</script>
2. Nitro 서버 캐시
서버 API 응답 자체를 캐시한다. DB 부하를 크게 줄일 수 있다.
cachedEventHandler
// server/api/products.ts
export default cachedEventHandler(async (event) => {
// DB에서 데이터 조회
const products = await db.from('products').select('*')
return products
}, {
maxAge: 60 * 60, // 1시간 캐시
name: 'products', // 캐시 키 이름
getKey: (event) => {
// 쿼리 파라미터에 따라 다른 캐시 키 사용
const query = getQuery(event)
return `products-${query.category}-${query.page}`
},
// 캐시 무효화 조건
shouldBypassCache: (event) => {
return getHeader(event, 'Cache-Control') === 'no-cache'
}
})
cachedFunction
// server/utils/db.ts
export const getCachedProducts = cachedFunction(
async (category: string) => {
return await db.from('products').select('*').eq('category', category)
},
{
maxAge: 60 * 10, // 10분 캐시
name: 'get-products',
getKey: (category) => category,
}
)
3. routeRules 캐시 (ISR)
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// ISR: 첫 요청 후 60초간 캐시, 만료 시 백그라운드에서 재생성
'/products/**': { isr: 60 },
// SSG: 빌드 시 생성, 변경 없음
'/blog/**': { prerender: true },
// CDN 캐시 헤더 설정
'/api/public/**': {
headers: {
'Cache-Control': 's-maxage=3600, stale-while-revalidate=86400'
}
},
// 캐시 완전 비활성화
'/api/realtime/**': {
headers: { 'Cache-Control': 'no-store' }
}
}
})
4. 컴포넌트 레벨 캐시
lazy 로딩 + 조건부 요청
<script setup>
const isTabActive = ref(false)
// isTabActive가 true가 될 때만 데이터 요청
const { data } = await useFetch('/api/analytics', {
immediate: false, // 즉시 요청하지 않음
})
watch(isTabActive, (active) => {
if (active) refresh()
})
</script>
keep-alive로 컴포넌트 캐시
<!-- 페이지 전환해도 컴포넌트 상태 유지 -->
<NuxtPage :keepalive="{ include: ['ProductList', 'Dashboard'] }" />
// pages/products/index.vue
defineOptions({
name: 'ProductList' // keepalive에서 참조하는 이름
})
5. 실전 캐시 전략 예시
상품 목록 (자주 안 바뀜)
// nuxt.config.ts
routeRules: {
'/products': { isr: 3600 } // 1시간마다 재생성
}
// server/api/products.ts
export default cachedEventHandler(handler, {
maxAge: 60 * 60, // Nitro도 1시간 캐시
})
사용자 전용 데이터 (캐시 불가)
<script setup>
// server: false로 서버 캐시 우회
// 사용자별 데이터는 서버에서 캐시하면 안 됨
const { data: myOrders } = await useFetch('/api/my/orders', {
server: false, // 클라이언트에서만 요청
headers: useRequestHeaders(['cookie']) // 인증 쿠키 전달
})
</script>
실시간 데이터 (폴링)
<script setup>
const { data, refresh } = await useFetch('/api/stock', {
server: false
})
// 30초마다 자동 갱신
let interval: ReturnType<typeof setInterval>
onMounted(() => {
interval = setInterval(refresh, 30_000)
})
onUnmounted(() => {
clearInterval(interval)
})
</script>
요약
| 전략 | 사용 시점 | 구현 방법 |
|---|---|---|
| Payload 캐시 | SSR 초기 로딩 중복 방지 | useFetch 기본 동작 |
| 수동 refresh | 변경 후 최신 데이터 필요 | refresh() 호출 |
| watch 자동 재요청 | 필터/페이지 변경 시 | watch 옵션 |
| Nitro 서버 캐시 | DB 부하 절감 | cachedEventHandler |
| ISR | 준정적 콘텐츠 | routeRules: { isr: N } |
| keep-alive | 페이지 전환 시 상태 유지 | <NuxtPage keepalive> |
| 폴링 | 실시간 데이터 | setInterval + refresh |