글 목록

next/image - Error handling

2023년 7월 3일 2분 읽기
Next.js

회사 코드를 리팩토링하던 중 이미지 렌더링 문제를 발견했습니다.

null 체크만으로는 부족했다

문제가 된 코드는 이렇게 생겼습니다.

tsx
<Image
  alt={"이미지"}
  src={imgUrl ? imgUrl : DEFAULT_IMG_URL}
  width={width}
  height={height}
/>

API 응답으로 받은 imgUrl이 null이나 undefined이면 기본 이미지를 보여주고, 값이 있으면 그대로 렌더링하는 구조입니다. 얼핏 보면 충분해 보이지만, 이 방식은 "경로가 존재하는가"만 체크할 뿐입니다.

실제로 문제가 된 상황은 달랐습니다. 서버에서 "admin/img1" 같은 경로를 정상적으로 전달받아도, 해당 경로에 파일이 없거나 이미지가 손상된 경우가 있었습니다. imgUrl이 null이 아니니 조건문을 통과하고, 브라우저는 그 URL로 실제 HTTP 요청을 보냅니다. 응답이 404이거나 파싱에 실패하면 깨진 이미지 아이콘이 그대로 화면에 남게 되죠.

null 체크로는 JavaScript 변수의 상태만 알 수 있었습니다. 실제 파일을 불러올 수 있는지는 네트워크 요청 이후에야 알 수 있습니다. 따라서 이미지 로드 실패를 별도로 처리해야 했습니다.

로드에 실패하면 state를 바꿔 기본 이미지를 보여준다

next/image 컴포넌트는 onError 이벤트 핸들러를 제공합니다. 이미지 로드에 실패했을 때 호출되는 콜백입니다. 여기서 state를 바꾸면 폴백 이미지를 보여줄 수 있습니다.

tsx
import { useState } from "react";
import Image from "next/image";

const DEFAULT_PROFILE_IMG_URL = "/images/default-profile.png";

interface ImageProps {
  alt: string;
  src: string;
  width: number;
  height: number;
}

export default function ImageWithFallback({
  alt,
  src,
  height,
  width,
}: ImageProps) {
  const [imgSrc, setImgSrc] = useState<string>(src);

  return (
    <Image
      alt={alt}
      src={imgSrc}
      width={width}
      height={height}
      onError={() => setImgSrc(DEFAULT_PROFILE_IMG_URL)}
    />
  );
}

동작 흐름은 단순합니다. imgSrc state를 props로 받은 src로 초기화한 뒤, 이미지 로드에 실패하면 onError 핸들러가 imgSrc를 기본 이미지 경로로 교체합니다. state가 바뀌면서 컴포넌트가 리렌더링되고, 이번엔 안전한 경로의 이미지가 표시되죠.

이제 두 가지 상황에 모두 대응할 수 있습니다. imgUrl이 null인 경우는 부모에서 기본 경로를 내려주면 되고, 경로는 있지만 실제 파일에 문제가 있는 경우는 onError가 자동으로 폴백 처리해줍니다.

폴백 이미지도 깨질 수 있다

폴백 이미지도 로드에 실패할 수 있습니다. 그렇게 되면 onError가 다시 호출되고, 또 onError가 호출되는 무한 루프가 생길 수 있죠. 간단하게는 현재 imgSrc가 이미 기본 이미지 경로인지 확인해서 핸들러를 일찍 종료하는 방식으로 막을 수 있습니다.

tsx
onError={() => {
  if (imgSrc !== DEFAULT_PROFILE_IMG_URL) {
    setImgSrc(DEFAULT_PROFILE_IMG_URL);
  }
}}

작은 처리지만, 프로덕션에서는 챙겨두는 편이 안전할 것 같습니다.