Supabase Storage에 이미지를 덮어썼는데, 왜 이전 이미지가 보일까?

최종욱·2025년 4월 22일

Uuno

목록 보기
1/7
post-thumbnail

문제 상황

unno 프로젝트에서 명함을 이미지로 저장할 때, 다음과 같은 경로로 Supabase Storage에 PNG 파일을 저장하도록 설계하였다.

const fileName = `${label}_${lastSlug}_img.png`;
const filePath = `${userId}/${lastSlug}/${fileName}`;

즉, 하나의 slug에 대해 front_img.png, back_img.png 같은 고정된 경로에 저장되는 구조다.

정상 케이스

  • 최초 저장 시에는 문제없이 잘 작동한다.

문제 발생

같은 slug로 다시 저장(즉, 이미지 덮어쓰기)하면 다음 문제가 발생했다:

  • Supabase에는 새로운 이미지로 덮어쓰기가 되었음에도,
  • 브라우저에서는 이전 이미지가 계속 캐시된 채로 보임
  • 강력 새로고침 (Cmd+Shift+R)을 해야만 새로운 이미지가 반영됨

현상 요약

항목설명
저장 방식동일 경로(/cards/{slug}_img.png)에 파일을 덮어씀
브라우저 반응이전 이미지가 계속 캐시되어 보임
Supabase 상태파일은 분명 최신 버전으로 저장됨
사용자 경험"왜 저장했는데 반영이 안 돼?" 라는 혼란 발생

원인: Supabase의 CDN 캐싱 구조

Supabase는 Cloudflare 기반 CDN을 통해 전 세계에 분산된 이미지 서버를 제공한다.

출처: Supabase Smart CDN 소개

캐시가 동작하는 방식 요약

  1. 사용자가 업로드한 이미지가 Supabase Storage에 저장된다.

  2. Supabase는 이를 Cloudflare CDN에 자동 복제(Cache) 한다.

  3. 이후 동일한 URL 요청이 들어오면 CDN에서 캐시된 이미지를 반환한다.

  4. 즉, 원본이 변경되어도, 캐시가 만료되기 전까지는 이전 이미지가 계속 반환된다.

참고 문서:
Supabase Smart CDN 공식 문서


해결 방법

1. 쿼리 스트링을 활용한 Cache Busting

const timestamp = new Date().getTime();
const imageUrl = `https://.../cards/${fileName}?v=${timestamp}`;
  • Supabase는 URL 쿼리를 무시하고 동일한 리소스를 반환하지만,

  • 브라우저는 ?v=12345678이 붙은 걸 새 파일로 취급하며 캐시를 우회한다.

2. 파일명을 아예 새로 생성

const fileName = `${label}_${lastSlug}_${Date.now()}_img.png`;
  • 경로 자체를 변경함으로써 캐시를 완전히 피할 수 있다.

  • 다만 기존 파일을 수동으로 정리해줘야 하고, 저장소 관리가 복잡해질 수 있다.

3. Supabase에서 캐시 TTL 조정은 불가능

  • 현재는 사용자가 TTL(Time To Live) 값을 설정하거나 purge(강제 초기화)하는 기능은 제공되지 않는다.

  • Smart CDN은 Pro 요금제에서만 자동 무효화를 제공하며, Free 요금제에서는 직접 우회 처리를 해야 한다.

결론

  • Supabase Storage는 CDN 기반이라 이미지 덮어쓰기가 바로 반영되지 않는다.
  • 쿼리 스트링을 붙이는 방식의 캐시 무효화가 가장 간단하고 안전한 해결책이다.
  • 이미지 URL을 클라이언트에서 불러올 때 ?v=${timestamp}를 함께 붙이자
profile
항상 “Why?”로 시작하는 프론트엔드 개발자

0개의 댓글