lifecarelog
개발일지

이미지 미리보기가 메모리를 먹는 이유 — createObjectURL 정리하기

이미지 업로드 미리보기를 만들다 메모리가 계속 늘던 문제를, createObjectURL을 revoke하지 않아 생긴 일과 useEffect 정리 함수로 잡은 과정으로 풀었어요.

4분 읽기

이미지 업로드 폼을 만들 때 미리보기는 거의 기본이잖아요. 파일을 고르면 화면에 바로 썸네일이 뜨는 그 기능이요. 저도 별생각 없이 URL.createObjectURL(file) 한 줄로 붙였는데, 한참 뒤에 개발자 도구를 열어 보니 메모리가 슬금슬금 늘고 있었어요.

미리보기 한 줄의 대가

createObjectURL은 파일을 가리키는 짧은 주소(blob URL)를 만들어 줘요. 편하긴 한데 함정이 하나 있어요. 이렇게 만든 주소는 브라우저 메모리에 원본 blob을 붙잡아 두고, 페이지를 떠나기 전까지 자동으로 놓아주지 않아요.

문제는 사용자가 파일을 여러 번 바꿀 때예요. 다른 사진을 고를 때마다 새 blob URL이 생기는데, 이전 것은 그대로 메모리에 남아 있어요. 요즘 스마트폰 사진 한 장이 보통 35MB니까, 미리보기를 20번만 갈아 끼워도 60100MB가 그냥 잡혀 있는 셈이죠.

파일 선택createObjectURL미리보기 표시(revoke 없음)blob이 메모리에 잔류

직접 재현해 보니

의심이 가서 같은 파일을 50번쯤 반복해서 다시 고르는 걸 돌려 봤어요. 힙 스냅샷을 찍어 보니 detached blob이 계속 쌓이더라고요. 코드는 멀쩡해 보이는데 메모리만 늘어서, 처음엔 컴포넌트 어딘가를 의심했는데 원인은 이 한 줄이었어요.

해결은 revoke 한 줄

해법도 결국 한 줄이에요. 새 URL을 만들기 전이나 컴포넌트가 사라질 때 URL.revokeObjectURL(url)로 이전 주소를 놓아주면 돼요. 리액트라면 useEffect 정리 함수에 넣는 게 깔끔해요.

useEffect(function syncPreview() {
  const url = URL.createObjectURL(file);
  setPreviewUrl(url);
  return () => URL.revokeObjectURL(url);
}, [file]);

정리 함수를 붙이고 다시 테스트해 봤더니, 50번을 갈아 끼워도 힙이 제자리로 돌아왔어요. 파일이 바뀌면 이전 blob은 revoke되고, 화면을 떠나면 마지막 것도 정리되는 거죠.

  • createObjectURL은 수동 해제 필요
  • revokeObjectURL로 정리
  • useEffect cleanup에 배치

작은 습관 하나

createObjectURL과 revokeObjectURL은 항상 짝으로 다닌다고 생각하는 게 편하더라고요. 만드는 코드를 쓰는 순간 놓아주는 코드도 같이 적어 두면, 나중에 원인 모를 메모리 증가로 헤맬 일이 줄어요.

플랜비(plan-b)에서 멤버 사진이나 전술보드 이미지를 다룰 때도 이 패턴을 그대로 쓰고 있어요. 혼자 운영하다 보면 이런 작은 누수가 오래 쌓여 문제가 되기 쉬운데, 짝을 맞추는 습관 하나로 꽤 많은 걸 막을 수 있었어요.

#프론트엔드#메모리 누수#createObjectURL

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.