티스토리 뷰
00. TL;DR
Nuxt 3에서 데이터 패치와 컴포넌트 렌더링 중 발생할 수 있는 오류에 대해 3가지 대응 방식을 설명합니다.
- 페이지 레벨 데이터 패치 오류 – SSR/CSR 전역 처리 (
useAsyncData+error.vue) - 컴포넌트 단위 데이터 패치 오류 – CSR 대응 (
useFetch+ErrorBoundary) - 일반 렌더링 로직 오류 – 런타임 예외 대응 (
ErrorBoundary)
원칙:
- 페이지 단의 오류는 Nuxt의 에러 핸들링 시스템과
layouts/error.vue를 통해 처리 - 컴포넌트 레벨 오류는
<NuxtErrorBoundary>로 로컬하게 처리 - 로딩은 각 컴포넌트 내부에서
ref()로 상태 관리하며<Loader>컴포넌트로 UI 분리
01. Nuxt의 전체 오류 처리 체계
| 처리 범위 | 위치 | 처리 방법 | SSR/CSR 대응 | UI 처리 |
|---|---|---|---|---|
| 페이지 초기 데이터 패치 실패 | useAsyncData |
throw createError() |
SSR/CSR 모두 | layouts/error.vue |
| 컴포넌트 내부 fetch 실패 | useFetch |
throw |
CSR 중심 | <NuxtErrorBoundary> |
| 일반 렌더링/로직 오류 | 모든 컴포넌트 | 런타임 throw |
CSR | <NuxtErrorBoundary> |
02. 페이지 단위 오류 처리 구조
02.01. 코드 예시 – useAsyncData 오류 처리
<script lang="ts" setup>
const { data: pageData, error } = await useAsyncData<{ title: string }>('pageData', () =>
$fetch('/api/page')
)
if (error.value) {
throw createError({
statusCode: error.value.statusCode ?? 500,
statusMessage: '페이지 데이터를 불러올 수 없습니다'
})
}
</script>
<template>
<div>
<h1>{{ pageData.title }}</h1>
<ErrorBoundary><UserCard /></ErrorBoundary>
</div>
</template>
02.02. 전역 에러 페이지 – layouts/error.vue
<template>
<div class="p-6 text-center text-red-700">
<h1 class="text-2xl font-bold">에러 발생</h1>
<p class="mt-2">상태코드: {{ error.statusCode }}</p>
<p>{{ error.message }}</p>
<button @click="navigateTo('/')">홈으로</button>
</div>
</template>
<script lang="ts" setup>
defineProps<{ error: Error & { statusCode?: number } }>()
</script>
03. 컴포넌트 단위 오류 처리 구조
03.01. UserCard.vue
<script lang="ts" setup>
const user = ref<{ name: string }>()
const loading = ref(true)
const loadUser = async () => {
loading.value = true
try {
const { data } = await useFetch<{ name: string }>('/api/user', {
onFetchError({ error }) {
throw error
}
})
if (!data.value?.name) throw new Error('이름 정보 없음')
user.value = data.value
} finally {
loading.value = false
}
}
defineExpose({ retry: loadUser })
onMounted(loadUser)
</script>
<template>
<Loader :loading="loading">
<p>{{ user?.name }}</p>
</Loader>
</template>
03.02. ErrorBoundary.vue
<template>
<NuxtErrorBoundary>
<slot :expose="exposeChild" />
<template #error="{ error, clearError }">
<div class="bg-red-100 p-4 text-red-700 border rounded">
<p>{{ error.message }}</p>
<button @click="() => { clearError(); childRef?.retry?.() }">재시도</button>
</div>
</template>
</NuxtErrorBoundary>
</template>
<script lang="ts" setup>
const childRef = ref<any>()
const exposeChild = (el: unknown) => (childRef.value = el)
</script>
03.03. Loader.vue
<template>
<slot v-if="!loading" />
<slot name="loading" v-else>
<div class="text-gray-500">로딩 중...</div>
</slot>
</template>
<script lang="ts" setup>
defineProps<{ loading: boolean }>()
</script>
💡 커스텀 로더 예시
<Loader :loading="isLoading">
<template #loading>
<div class="flex items-center justify-center py-4">
<Icon name="lucide:loader" class="animate-spin mr-2" />
<span>불러오는 중입니다...</span>
</div>
</template>
<p>콘텐츠 로딩 완료!</p>
</Loader>
04. 일반 렌더링 로직 오류 대응 구조
04.01. ProductPrice.vue
<script lang="ts" setup>
const props = defineProps<{ price: number }>()
const formattedPrice = `$${props.price.toFixed(2)}`
</script>
<template>
<p>{{ formattedPrice }}</p>
</template>
05. 전체 페이지 구성 예시
<template>
<h1>{{ pageData.title }}</h1>
<ErrorBoundary>
<UserCard />
</ErrorBoundary>
<ErrorBoundary>
<ProductList />
</ErrorBoundary>
<ErrorBoundary>
<ProductPrice :price="undefined" />
</ErrorBoundary>
</template>
<script lang="ts" setup>
import ErrorBoundary from '@/components/common/ErrorBoundary.vue'
import Loader from '@/components/common/Loader.vue'
import UserCard from '@/components/UserCard.vue'
import ProductList from '@/components/ProductList.vue'
import ProductPrice from '@/components/ProductPrice.vue'
const { data: pageData, error } = await useAsyncData<{ title: string }>('pageData', () =>
$fetch('/api/page')
)
if (error.value) {
throw createError({ statusCode: 500, statusMessage: '페이지 데이터 로딩 실패' })
}
</script>
06. 전체 흐름 요약
| 레벨 | API 호출 | 오류 발생 시 | 로딩 표시 | 책임 분리 |
|---|---|---|---|---|
페이지 (useAsyncData) |
SSR + CSR | createError() → error.vue |
v-if="!data" 등 직접 처리 |
최상단 |
컴포넌트 (useFetch) |
CSR | throw → ErrorBoundary |
내부 loading + Loader |
각 컴포넌트 단위 |
| 일반 렌더링 | 없음 | 런타임 오류 → ErrorBoundary |
없음 | 각 컴포넌트 단위 |
07. 결론
- 페이지 단위의 SSR/CSR 오류는
useAsyncData + createError()와layouts/error.vue로 대응합니다. - 컴포넌트 단위 fetch 오류와 렌더링 오류는
<ErrorBoundary>로 격리 처리합니다. - 로딩은 컴포넌트 내부에서 관리하고
<Loader>컴포넌트로 분리하며, 필요 시#loading슬롯으로 UI 커스터마이징이 가능합니다. - 재시도는
defineExpose({ retry })를 통해 상위 컴포넌트와 인터페이스를 연결합니다.
'FrontEnd > nuxt' 카테고리의 다른 글
| Vite/Nuxt 대용량 프로젝트, 로컬 네트워크 접속 시 리소스 타임아웃(ERR_TIMED_OUT) 문제 해결 (0) | 2025.08.21 |
|---|
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 모노레포 스크립트
- prototype
- string
- object literal
- pakage-lock.json
- nuxt
- react-router
- 바이트 코드
- deep dive
- refrerence
- ViTE
- double-linked-list
- JavaScript
- primitive
- uselazyasyncdata
- webpack
- useasyncdata
- interning
- vue
- pnpm 명령어
- vee-validate
- premitive
- TypeScript
- string table
- JIT
- library mode
- bundler
- npm ci
- react
- scoped slot
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
글 보관함
