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로 바뀔 때 레이아웃이 흔들리지 않는다.