blog
이미지 로딩 실패 시 대체 UI 보여주기: onerror와 Vue·React 처리
Front-end / JavaScript#img#onerror#Vue#React#UX
이미지 경로가 잘못되었거나 서버에 문제가 있으면 브라우저는 깨진 이미지 아이콘을 보여준다. 로딩 실패를 감지해 대체 UI나 기본 이미지를 보여주는 방법을 정리했다.
1. HTML: onerror 속성
<img
src="invalid-url.jpg"
alt="상품 이미지"
onerror="this.hidden = true; this.nextElementSibling.hidden = false"
/>
<div class="image-fallback" hidden>이미지를 불러오지 못했습니다</div>
간단하지만 인라인 이벤트 핸들러는 CSP(script-src에 'unsafe-inline'이 없는 경우)에서 차단된다. 또한 스크립트가 붙기 전에 이미 실패한 이미지는 addEventListener('error')로 잡지 못할 수 있다. 프레임워크를 쓴다면 프레임워크의 이벤트 바인딩을 사용한다.
2. Vue
<script setup lang="ts">
import { ref, watch } from 'vue';
const props = defineProps<{ src: string; alt: string }>();
const hasError = ref(false);
// src가 바뀌면 다시 시도
watch(() => props.src, () => {
hasError.value = false;
});
</script>
<template>
<div class="relative h-[200px] w-[300px]">
<img
v-if="!hasError"
:src="src"
:alt="alt"
class="h-full w-full object-cover"
@error="hasError = true"
/>
<div v-else class="absolute inset-0 flex items-center justify-center text-gray-400">
이미지를 불러오지 못했습니다
</div>
</div>
</template>
3. React
import { useState } from 'react';
function ImageWithFallback({ src, alt }: { src: string; alt: string }) {
const [hasError, setHasError] = useState(false);
return (
<div className="relative h-[200px] w-[300px]">
{!hasError ? (
<img
src={src}
alt={alt}
className="h-full w-full object-cover"
onError={() => setHasError(true)}
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-gray-400">
이미지를 불러오지 못했습니다
</div>
)}
</div>
);
}
src가 바뀌었을 때 에러 상태를 초기화하려면, 부모에서 <ImageWithFallback key={src} ... />처럼 key를 주어 컴포넌트를 새로 만드는 방법이 가장 간단하다.
4. 기본 이미지로 바꾸기
대체 UI 대신 기본 이미지(예: 기본 프로필 사진)를 보여줄 수도 있다.
<img :src="hasError ? fallbackSrc : src" :alt="alt" @error="hasError = true" />
<img src={hasError ? fallbackSrc : src} alt={alt} onError={() => setHasError(true)} />
기본 이미지는 번들에 포함된 로컬 파일을 쓴다. 기본 이미지마저 외부 URL이라 실패하면 다시 error가 발생하는데, 위 코드는 hasError가 이미 true라 무한 반복되지는 않지만 결국 깨진 이미지가 보인다.
마무리
- 로딩 실패는 항상 일어날 수 있다는 전제로 대체 UI를 준비한다.
alt텍스트는 대체 UI와 별개로 항상 넣는다. 스크린 리더 사용자에게는 이것이 이미지의 내용이다.- 이미지 영역의 크기를 고정해 두면, 대체 UI로 바뀔 때 레이아웃이 흔들리지 않는다.