이 글은 img-compressor의 결과 카드와 삭제 흐름, media-crop의 영상 편집 화면을 기준으로 작성했습니다. 브라우저에서 파일을 서버로 보내지 않고 처리할 때는 미리보기 URL의 수명도 직접 관리해야 하는데, 이미지 도구와 영상 도구에서 정리 시점이 어떻게 달라지는지 함께 봅니다.
화면에서 사라졌다고 끝난 게 아니었다
URL.createObjectURL은 사용자가 선택한 파일이나 변환 결과를 빠르게 미리보기로 보여 주기에 좋습니다. 문제는 이 URL이 React 상태에서 사라졌다고 해서 곧바로 해제되는 것이 아니라는 점입니다. 파일을 여러 번 추가하고 삭제하는 도구라면 이 차이가 중요합니다. 사용자가 20장의 이미지를 넣고, 일부를 지우고, 다시 20장을 넣는 흐름이 반복될 수 있는데, 이때 만들어진 URL을 정리하지 않으면 브라우저가 이전 파일 데이터를 계속 붙잡고 있을 수 있습니다.
이미지 도구: 삭제 동작을 두 종류로 나눈다
img-compressor 같은 결과 목록 UI에서는 개별 삭제와 전체 삭제가 모두 필요합니다. 개별 삭제는 특정 카드의 URL만 해제하고 배열에서 제거합니다. 전체 삭제는 현재 목록에 있는 모든 URL을 순회하면서 해제한 뒤 상태를 비웁니다.
images.forEach((image) => {
URL.revokeObjectURL(image.url);
if (image.webpUrl) {
URL.revokeObjectURL(image.webpUrl);
}
});
WebP 변환 결과의 URL도 따로 확인하는 이유는 한 카드에 원래 압축 결과와 WebP 결과가 함께 존재할 수 있기 때문입니다. 카드가 하나라고 해서 URL도 하나라고 가정하면 변환 후 만들어진 링크가 남을 수 있습니다. 정리 누락을 막으려면 먼저 결과 카드 하나에 URL이 정확히 몇 개 만들어지는지 세어 봐야 합니다.
다운로드 직후에 바로 해제하지 않는 이유
다운로드 버튼을 누른 직후 URL을 바로 해제하는 방식도 가능하지만, 결과 카드를 계속 화면에 보여 주는 도구에서는 조심해야 합니다. 사용자가 같은 파일을 다시 다운로드하거나, WebP 변환 전후를 비교할 수 있어야 한다면 카드가 살아 있는 동안 URL도 유지되어야 합니다. 중요한 기준은 URL을 만든 코드가 아니라, 사용자가 그 URL을 다시 사용할 가능성이 있는 화면 상태입니다.
영상 도구: 입력 URL과 결과 URL은 생명주기가 다르다
media-crop의 영상 편집 화면에서는 같은 원칙이 조금 다른 모양으로 나타납니다. 사용자가 파일을 선택하면 그 파일을 재생 화면에 보여 주기 위한 URL이 즉시 필요합니다. 반면 잘라낸 결과 파일의 URL은 사용자가 자르기 버튼을 눌러 처리가 끝난 뒤에야 생깁니다. 이 두 URL은 생성 시점도, 사라져야 하는 시점도 다릅니다. media-crop은 이 차이를 하나의 정리 로직으로 합치지 않고, 각각의 의존성을 가진 별도의 useEffect로 나눕니다.
useEffect(() => {
const url = URL.createObjectURL(file);
setFileUrl(url);
return () => {
if (url) URL.revokeObjectURL(url);
};
}, [file]);
useEffect(() => {
return () => {
if (processedUrl) URL.revokeObjectURL(processedUrl);
};
}, [processedUrl]);
첫 번째 effect는 file prop이 바뀔 때마다 새 URL을 만들고, 그 전에 만들었던 URL은 cleanup 함수에서 해제합니다. 두 번째 effect는 processedUrl이 바뀔 때만 동작하며, 이전 결과 URL을 정리하는 역할만 합니다. 두 URL을 한 곳에서 같이 관리하면 "입력 파일이 바뀌었을 때 결과 URL까지 같이 지워야 하는가"처럼 굳이 풀지 않아도 될 조건이 늘어납니다.
두 사례를 같이 보면 알 수 있는 것
img-compressor는 여러 파일을 배열로 관리하기 때문에 전체 삭제와 개별 삭제 시점에 한꺼번에 revokeObjectURL을 호출하는 방식이 자연스러웠습니다. media-crop은 한 번에 파일 하나만 다루지만, 그 한 파일 안에서도 "원본 미리보기"와 "처리 결과"라는 서로 다른 생명주기를 가진 리소스가 함께 존재합니다. 두 사례를 같이 보면, Object URL 정리 패턴이 "배열인가 단일 값인가"보다 "이 URL이 언제 더 이상 필요 없어지는가"라는 질문에 더 좌우된다는 점을 알 수 있습니다.
한계와 다음 확인 항목
Object URL 정리는 성능 점수를 즉시 올리는 기능이라기보다, 브라우저 파일 도구의 안정성을 지키는 기본 작업입니다. 실제 메모리 회수 시점은 브라우저 구현과 상황에 따라 달라질 수 있으므로, 개발자 도구의 메모리 패널로 반복 처리 시나리오를 확인하는 것이 좋습니다. 현재 media-crop 구현은 컴포넌트가 언마운트될 때 두 effect의 cleanup이 모두 실행되는 것에 의존하는데, 사용자가 자르기 처리 중에 페이지를 떠나는 경우처럼 진행 중인 ffmpeg 작업과 URL 정리가 겹치는 상황은 아직 별도로 테스트하지 못했습니다. 컴포넌트 언마운트 시 현재 목록의 URL을 해제하는 흐름도 img-compressor 쪽에서 함께 점검할 대상입니다.