Files
gil-wiki/wiki/nuxt-data-fetching.md
gil 5f664546cf feat: 위키 저장소 초기 커밋
- CLAUDE.md 운영 규칙
- wiki/ 정리된 지식 페이지 (Nuxt + Claude Code)
- raw/ 원본 자료
- reference/ Nuxt 4.x 공식 문서

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 00:31:51 +09:00

237 lines
6.1 KiB
Markdown

# Nuxt 데이터 패칭
> **카테고리:** 핵심 개념
> **최종 수정:** 2026-05-13
> **관련:** [[nuxt-lifecycle]], [[nuxt-state-management]], [[nuxt-rendering-modes]]
## 요약
`$fetch`, `useFetch`, `useAsyncData` 세 가지가 있다. SSR 환경에서 이중 패칭을 막으려면 `useFetch` 또는 `useAsyncData`를 써야 한다. `$fetch`는 이벤트 핸들러(사용자 액션) 전용.
---
## 세 가지 비교
| | `$fetch` | `useFetch` | `useAsyncData` |
|---|---|---|---|
| SSR 중복 방지 | ❌ | ✅ | ✅ |
| 캐싱/재사용 | ❌ | ✅ (URL이 key) | ✅ (명시적 key) |
| 반응형 반환값 | ❌ | ✅ | ✅ |
| 사용 위치 | 어디서나 | setup 함수 내부 | setup 함수 내부 |
| 적합한 상황 | 폼 제출, 버튼 클릭 | 일반 데이터 로딩 | CMS/서드파티 쿼리 레이어 |
---
## `$fetch`
[ofetch](https://github.com/unjs/ofetch) 기반. 전역 자동 임포트.
```typescript
// 이벤트 핸들러에서 사용 (올바른 패턴)
async function submitForm() {
const result = await $fetch('/api/submit', {
method: 'POST',
body: { name: '홍길동' },
})
}
```
> ⚠️ 주의: `<script setup>`의 최상위 레벨에서 `$fetch`를 `await`하면 서버·클라이언트 양쪽에서 각각 실행되어 **이중 패칭** 발생. 반드시 `useFetch` 또는 `useAsyncData` 사용.
---
## `useFetch`
`useAsyncData + $fetch`의 편의 래퍼. URL 자체가 캐시 key.
```vue
<script setup lang="ts">
// 기본 사용
const { data, status, error, refresh, execute, clear } = await useFetch('/api/posts')
// 타입 지정
const { data: posts } = await useFetch<Post[]>('/api/posts')
</script>
```
### 주요 반환값
| 값 | 설명 |
|---|---|
| `data` | 응답 데이터 (ref) |
| `status` | `'idle' \| 'pending' \| 'success' \| 'error'` |
| `error` | 에러 객체 (ref) |
| `refresh()` / `execute()` | 수동 재패칭 |
| `clear()` | data를 undefined로 초기화 |
---
## `useAsyncData`
CMS, 서드파티 쿼리 레이어, 복수 요청 묶기에 적합. 명시적 key 권장.
```vue
<script setup lang="ts">
// 기본
const { data } = await useAsyncData('users', () => myGetFunction('users'))
// 동적 key (라우트 파라미터 활용)
const { id } = useRoute().params
const { data: user } = await useAsyncData(`user:${id}`, () => fetchUser(id))
// 복수 요청 병렬화
const { data } = await useAsyncData('cart', async (nuxtApp, { signal }) => {
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons', { signal }),
$fetch('/cart/offers', { signal }),
])
return { coupons, offers }
})
```
> ⚠️ 주의: `useAsyncData`는 데이터 패칭·캐싱 전용. Pinia 액션 호출 같은 사이드 이펙트에 쓰면 null 값으로 반복 실행됨 → `callOnce` 사용.
---
## 주요 옵션
### `lazy` — 네비게이션 차단 해제
기본값은 데이터 로딩 완료까지 페이지 전환 차단. `lazy: true`면 차단 해제, 수동 로딩 상태 처리 필요.
```vue
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', { lazy: true })
// 또는 useLazyFetch('/api/posts')
</script>
<template>
<div v-if="status === 'pending'">로딩 중...</div>
<div v-else>{{ posts }}</div>
</template>
```
### `server: false` — 클라이언트 전용 패칭
```typescript
// hydration 완료 후에만 패칭
const { status, data } = useFetch('/api/comments', {
lazy: true,
server: false,
})
```
### `pick` / `transform` — 페이로드 크기 최소화
```typescript
// 필요한 필드만
const { data } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description'],
})
// 변환
const { data } = await useFetch('/api/mountains', {
transform: (mountains) => mountains.map(m => ({ title: m.title })),
})
```
### `watch` — 반응형 재패칭
```typescript
const id = ref(1)
const { data } = await useFetch('/api/users', { watch: [id] })
// id 변경 시 자동 재패칭
```
### Computed URL — 동적 URL 재패칭
```vue
<script setup lang="ts">
const id = ref(null)
// query 파라미터로 동적 URL
const { data } = useLazyFetch('/api/user', {
query: { user_id: id },
})
// URL 자체가 동적인 경우
const { data } = useLazyFetch(() => `/api/users/${id.value}`, {
immediate: false,
})
</script>
```
### `immediate: false` — 수동 실행
```vue
<script setup lang="ts">
const { data, execute, status } = await useLazyFetch('/api/comments', {
immediate: false,
})
</script>
<template>
<button v-if="status === 'idle'" @click="execute">데이터 불러오기</button>
<div v-else-if="status === 'pending'">로딩 중...</div>
<div v-else>{{ data }}</div>
</template>
```
---
## 캐싱과 키 공유
같은 key를 쓰는 컴포넌트는 동일한 `data`, `error`, `status` ref를 공유.
```typescript
// 같은 key → 동일 인스턴스 (옵션도 일관되어야 함)
const { data: users1 } = useAsyncData('users', fetchUsers, { deep: false })
const { data: users2 } = useAsyncData('users', fetchUsers, { deep: false }) // ✅
// 독립 인스턴스가 필요하면 다른 key 사용
const { data: users1 } = useAsyncData('users-1', fetchUsers)
const { data: users2 } = useAsyncData('users-2', fetchUsers)
```
반응형 key:
```typescript
const userId = ref('123')
const { data } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value),
)
// userId 변경 시 자동 재패칭 + 이전 캐시 정리
```
---
## 헤더·쿠키 전달
```typescript
// useFetch는 서버에서 useRequestFetch()로 클라이언트 헤더·쿠키 자동 프록시
const { data } = await useFetch('/api/echo')
// $fetch 직접 사용 시 수동으로 전달 필요
const headers = useRequestHeaders(['cookie'])
const data = await $fetch('/api/me', { headers })
```
---
## 자주 쓰는 유틸
| 유틸 | 용도 |
|---|---|
| `refreshNuxtData(key?)` | 특정 key 또는 전체 캐시 무효화 후 재패칭 |
| `clearNuxtData(key?)` | 캐시 데이터만 삭제 |
| `useNuxtData(key)` | 캐시된 데이터 읽기 전용 접근 |
---
## 참고 / 출처
- `reference/1.getting-started/10.data-fetching.md`
- `reference/4.api/2.composables/use-fetch.md`
- `reference/3.guide/5.recipes/3.custom-usefetch.md`