
안녕하세요! 오늘은 웹 애플리케이션의 안정성과 사용자 경험을 크게 향상시킬 수 있는 중요한 패턴, **에러 바운더리(Error Boundary)**에 대해 이야기하려 합니다. React에서 유래된 개념이지만, Vue.js에서도 충분히 구현 가능하며, 실제로 저희 프로젝트에 적용한 사례를 통해 그 효용성을 보여드릴게요.
기존의 에러 처리 방식은 몇 가지 심각한 문제를 안고 있었습니다.
try...catch 블록을 사용해 에러를 처리했습니다. 이는 코드의 중복을 낳고, 일관성 없는 에러 메시지를 사용자에게 보여주게 만들었습니다.try...catch로 잡히지 않은 런타임 에러는 컴포넌트 트리를 타고 올라가 결국 전체 앱을 중단시키는 치명적인 결과를 초래했습니다.에러 바운더리는 React에서 처음 도입된 개념으로, 자식 컴포넌트 트리에서 발생하는 JavaScript 에러를 포착하고, 에러 로그를 기록하며, 에러가 발생한 컴포넌트 트리 대신 대체 UI를 렌더링하는 컴포넌트입니다. Vue.js에서는 이와 비슷한 역할을 하는 onErrorCaptured 훅을 사용해 에러 바운더리를 구현할 수 있습니다.
onErrorCaptured 훅은 자식 컴포넌트에서 발생한 에러를 포착할 수 있는 훅입니다. 이 훅을 활용하여 에러 바운더리 컴포넌트를 만들고, 앱의 특정 영역을 감싸주면 됩니다.
ErrorBoundary.vue가장 기본이 되는 에러 바운더리 컴포넌트입니다. onErrorCaptured로 하위 컴포넌트의 에러를 잡고, 에러가 발생하면 슬롯 콘텐츠(slot) 대신 사용자 친화적인 에러 UI를 보여줍니다.
<template>
<div v-if="error" class="error-boundary">
<el-result icon="error" title="오류가 발생했습니다" :sub-title="error.message">
<template #extra>
<el-button type="primary" @click="handleRetry">다시 시도</el-button>
<el-button @click="handleReset">초기화</el-button>
</template>
</el-result>
</div>
<div v-else>
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
const error = ref<Error | null>(null)
// 하위 컴포넌트에서 발생한 에러를 캐치
onErrorCaptured((err: Error, instance: any, info: string) => {
console.error('ErrorBoundary caught an error:', err, info)
error.value = err
// 에러 추적 서비스에 전송 (Sentry, LogRocket 등)
if (window.Sentry) {
window.Sentry.captureException(err, { extra: { component: instance?.$options?.name } })
}
return false // 에러가 상위 컴포넌트로 전파되는 것을 막음
})
const handleRetry = () => {
error.value = null // 에러 상태 초기화
window.location.reload() // 페이지 새로고침으로 컴포넌트 재마운트
}
const handleReset = () => {
// 앱의 상태를 완전히 초기화
localStorage.clear()
window.location.href = '/'
}
</script>
onErrorCaptured의 반환 값으로 false를 주면 에러가 더 이상 상위 컴포넌트로 전파되지 않아, 에러가 발생한 컴포넌트만 고립시킬 수 있습니다.
기본 에러 바운더리 외에도, 특정 상황에 맞춘 맞춤형 에러 바운더리를 만들 수 있습니다. 예를 들어, 차트 관련 컴포넌트에서만 발생하는 에러를 위한 ChartErrorBoundary를 만들어 UX를 더 세밀하게 제어할 수 있습니다.
<template>
<div v-if="chartError" class="chart-error-boundary">
<div class="chart-error-container">
<el-icon class="chart-error-icon"><DataAnalysis /></el-icon>
<h4>차트를 불러올 수 없습니다</h4>
<p>데이터 로딩 중 문제가 발생했습니다.</p>
<el-button size="small" @click="handleRetry">다시 시도</el-button>
</div>
</div>
<div v-else>
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
import { DataAnalysis } from '@element-plus/icons-vue'
const chartError = ref<Error | null>(null)
onErrorCaptured((err: Error) => {
// 특정 에러만 캐치하도록 조건 추가
if (err.message.includes('Chart') || err.message.includes('canvas')) {
chartError.value = err
return false
}
return true // 다른 종류의 에러는 상위로 전파
})
const handleRetry = () => {
chartError.value = null
}
</script>
이런 방식으로 ApiErrorBoundary, PermissionErrorBoundary 등을 만들어 앱의 에러 처리 로직을 훨씬 더 유연하게 만들 수 있습니다.
App.vue 파일에 에러 바운더리를 적용하면 앱의 전반적인 안정성을 확보할 수 있습니다.
<template>
<div id="app">
<ErrorBoundary>
<template #default>
<el-container>
<el-aside width="200px"><Navigation /></el-aside>
<el-main><router-view /></el-main>
</el-container>
</template>
</ErrorBoundary>
</div>
</template>
<script setup lang="ts">
import ErrorBoundary from '@/components/common/ErrorBoundary.vue'
import Navigation from '@/components/Navigation.vue'
</script>
더 나아가, 특정 컴포넌트 트리에만 에러 바운더리를 적용하여 해당 영역에서 에러가 발생했을 때만 대체 UI를 보여주도록 할 수 있습니다.
<template>
<div class="dashboard">
<ErrorBoundary>
<template #default>
<ConnectionStatus />
<TopicSummary />
</template>
</ErrorBoundary>
<ChartErrorBoundary>
<template #default>
<MetricsDashboard />
</template>
</ChartErrorBoundary>
</div>
</template>
에러 바운더리 패턴은 앱의 안정성을 한 단계 끌어올리는 중요한 기술입니다. Vue.js의 onErrorCaptured 훅을 활용하면 어렵지 않게 구현할 수 있으며, 이로써 사용자에게 더 신뢰성 있는 서비스를 제공할 수 있습니다. 여러분의 프로젝트에도 에러 바운더리를 도입하여 견고한 애플리케이션을 만들어보세요!