Перейти к содержимому

16. Обработка ошибок

Nuxt предоставляет комплексную систему обработки ошибок как на клиенте, так и на сервере. Понимание этой системы критически важно для создания надёжных приложений.


error.vue — глобальная страница ошибок Nuxt. Она показывается вместо всего приложения при фатальных ошибках:

error.vue
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps<{
error: NuxtError
}>()
const handleError = () => clearError({ redirect: '/' })
const errorMessages = {
404: 'Страница не найдена',
403: 'Доступ запрещён',
500: 'Внутренняя ошибка сервера',
503: 'Сервис недоступен'
}
const message = computed(() =>
errorMessages[props.error.statusCode] || props.error.message
)
</script>
<template>
<div class="error-page">
<div class="error-code">{{ error.statusCode }}</div>
<h1>{{ message }}</h1>
<p v-if="error.statusCode !== 404">{{ error.message }}</p>
<button @click="handleError">
Вернуться на главную
</button>
</div>
</template>

useError() возвращает реактивное состояние текущей ошибки:

<script setup lang="ts">
const error = useError()
// Проверка наличия ошибки
if (error.value) {
console.log('Код ошибки:', error.value.statusCode)
console.log('Сообщение:', error.value.message)
console.log('Стек:', error.value.stack)
}
</script>
<template>
<div v-if="error">
Ошибка: {{ error.statusCode }}
</div>
</template>

createError() создаёт ошибку с полным контролем над её свойствами:

// В API-маршруте (server/api/users/[id].ts)
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await findUser(id)
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'Not Found',
message: `Пользователь с ID ${id} не найден`,
data: { userId: id }
})
}
return user
})
<!-- В компоненте страницы -->
<script setup lang="ts">
const { data, error } = await useFetch('/api/users/123')
if (error.value) {
throw createError({
statusCode: error.value.statusCode,
message: error.value.message,
fatal: true // делает ошибку фатальной — показывает error.vue
})
}
</script>

showError() программно показывает страницу ошибки:

// В composable
export function useRequireAuth() {
const user = useUser()
if (!user.value) {
showError({
statusCode: 401,
message: 'Необходима авторизация'
})
}
}
<script setup lang="ts">
const handleDelete = async (id: string) => {
try {
await $fetch(`/api/items/${id}`, { method: 'DELETE' })
} catch (err) {
// Показать страницу ошибки
showError({
statusCode: err.statusCode || 500,
message: 'Не удалось удалить элемент'
})
}
}
</script>

clearError() сбрасывает ошибку и опционально выполняет редирект:

// Сброс без редиректа
await clearError()
// Сброс с редиректом на главную
await clearError({ redirect: '/' })
// В компоненте
const handleRetry = async () => {
await clearError({ redirect: '/dashboard' })
}
error.vue
<template>
<div>
<h1>{{ error.statusCode }}</h1>
<div class="actions">
<button @click="clearError()">Попробовать снова</button>
<button @click="clearError({ redirect: '/' })">На главную</button>
</div>
</div>
</template>

Vue-хук для перехвата ошибок из дочерних компонентов:

<script setup lang="ts">
onErrorCaptured((err, instance, info) => {
console.error('Перехвачена ошибка:', err)
console.log('Компонент:', instance?.$options.name)
console.log('Откуда:', info)
// Отправка в Sentry
$sentry.captureException(err, {
extra: { component: instance?.$options.name, info }
})
// Вернуть false — предотвратить дальнейшее распространение
return false
})
</script>

Поведение ошибок отличается в зависимости от контекста:

server/api/data.ts
export default defineEventHandler(async (event) => {
// Ошибка на сервере
throw createError({
statusCode: 500,
message: 'Ошибка базы данных'
})
// → Показывает error.vue с серверной стороны
})
<!-- pages/data.vue — ошибка во время SSR -->
<script setup lang="ts">
const { data } = await useFetch('/api/data')
// Если useFetch бросает fatal error → error.vue во время SSR
</script>
<script setup lang="ts">
// Ошибка при взаимодействии пользователя
const handleAction = async () => {
try {
await riskyOperation()
} catch (err) {
// Мягкая ошибка — показываем в UI
errorMessage.value = err.message
// или
// Жёсткая ошибка — показываем error.vue
showError({ statusCode: 500, message: err.message })
}
}
</script>

Для кастомного 404 создайте файл error.vue с обработкой кода 404:

error.vue
<script setup lang="ts">
const props = defineProps<{ error: NuxtError }>()
const is404 = computed(() => props.error.statusCode === 404)
</script>
<template>
<!-- Специальный дизайн для 404 -->
<div v-if="is404" class="not-found">
<h1>404 — Страница не найдена</h1>
<p>Возможно, страница была перемещена или удалена.</p>
<NuxtLink to="/">Вернуться на главную</NuxtLink>
</div>
<!-- Общий дизайн для других ошибок -->
<div v-else class="error-generic">
<h1>Ошибка {{ error.statusCode }}</h1>
<p>{{ error.message }}</p>
<button @click="clearError({ redirect: '/' })">Попробовать снова</button>
</div>
</template>

composables/useApiData.ts
export function useApiData<T>(endpoint: string) {
const data = ref<T | null>(null)
const error = ref<string | null>(null)
const loading = ref(false)
const fetch = async () => {
loading.value = true
error.value = null
try {
data.value = await $fetch<T>(endpoint)
} catch (err: any) {
if (err.statusCode === 401) {
await navigateTo('/login')
} else if (err.statusCode === 404) {
error.value = 'Данные не найдены'
} else if (err.statusCode >= 500) {
// Критическая ошибка — показываем error.vue
throw createError({
statusCode: err.statusCode,
message: err.message,
fatal: true
})
} else {
error.value = err.message || 'Неизвестная ошибка'
}
} finally {
loading.value = false
}
}
return { data, error, loading, fetch }
}

Компонент для перехвата ошибок в части страницы без показа error.vue:

<template>
<NuxtErrorBoundary @error="handleError">
<!-- Основной контент -->
<template #default>
<ComplexWidget />
</template>
<!-- Fallback при ошибке -->
<template #error="{ error, clearError }">
<div class="widget-error">
<p>Виджет недоступен: {{ error.message }}</p>
<button @click="clearError()">Повторить</button>
</div>
</template>
</NuxtErrorBoundary>
</template>
<script setup lang="ts">
const handleError = (error: Error) => {
console.error('Ошибка виджета:', error)
// Не пробрасываем выше — ошибка изолирована
}
</script>

МетодКогда использоватьПоведение
createError()Создание структурированной ошибкиБросает или возвращает
showError()Показать error.vue программноПоказывает error.vue
clearError()Сброс текущей ошибкиУбирает ошибку, опц. редирект
useError()Читать текущую ошибкуРеактивное состояние ошибки
NuxtErrorBoundaryИзолировать ошибки компонентаЛокальный fallback
onErrorCapturedVue lifecycle хукПерехват из дочерних