logo

DowanKim

8. 이미지 로딩속도 개선은 항상 머리가 아프다.

2025년 11월 1일

졸업논문 대체 웹사이트

Firebase Storage에서 로컬 번들로 이미지,영상 관리방식을 전환하게 된 과정을 글로 작성하였습니다.

처음에는 Firebase Storage에서 이미지를 불러왔습니다. 그러나 전체적으로 이미지 로딩이 꽤 오래 걸리는 듯한 느낌이 들었고, 호버 애니메이션이 굉장히 많은 프로젝트인데 로딩지연으로 호버했을때 잠시동안 다른 이미지가 뜨다가 제대로 뜨게되는 것 같은 문제가 있었습니다. 여러 이유를 고려해 보았고,(괜히 파이어베이스 데베 위치가 싱가포르라서 그런거 아닐까 라는 억지 남탓까지도...) 결국 모든 이미지와 영상을 로컬에 포함해 빌드 시 번들에 포함하는 방식으로 전환했고, WebP 변환과 GIF→MP4 변환으로 최적화했습니다.


1. 초기 구현: Firebase Storage + Realtime Database

  • 방법: 모든 이미지·영상 파일을 Firebase Storage에 업로드하고, 해당 파일 URL을 Firebase Realtime Database의 각 디자이너 데이터에 저장하여 사용했습니다.
  • 장점:
    • 중앙 집중형 관리: 스토리지에서 파일만 교체하면, 별도의 코드 수정 없이 교체 가능
    • 데이터와 리소스 연동: 디자이너별 정보에 이미지 URL을 포함해 구조적으로 관리
// Firebase Database 구조 예시 { "designerInfo": { "박세은": { "Image": { "after": "https://firebasestorage.googleapis.com/.../after.jpg", "before": "https://firebasestorage.googleapis.com/.../before.jpg", "sub": "https://firebasestorage.googleapis.com/.../sub.jpg" } } } }
// src/services/designers.ts export async function fetchDesignerCards(): Promise<DesignerCardData[]> { const snapshot = await get(ref(db, "designerInfo")); const value = snapshot.val() as Record<string, RawDesignerNode> | null; return Object.entries(value ?? {}).map(([key, node]) => ({ name: node?.designerInfo?.name ?? key, projectName: node?.designerInfo?.conceptTitle ?? node?.Poster?.title ?? "", image: { after: node?.Image?.after ?? "", before: node?.Image?.before ?? "", sub: node?.Image?.sub ?? "", }, })); }

2. 문제 발견: 로딩 지연과 인터랙션 오류

2-1. 이미지 로딩 지연

  • Firebase Storage에서 이미지를 가져오기 위해 매번 개별 요청이 필요
  • 14명의 디자이너 × 이미지 3종(전/후/서브) = 42개 요청 → 첫 로딩 시 지연 체감
  • 네트워크 상태에 따라 이미지 표시가 늦어져 빈 영역이 보이는 경우 발생

2-2. 호버 애니메이션 충돌 (TeamSelect 페이지)

  • 팀 썸네일을 onMouseEnter / onMouseLeave로 전환하는 구조였으나, 이미지 로딩이 느리면 이전 요청이 뒤늦게 도착
  • 예: Web 팀 호버 → Web After 이미지 요청 → 사용자가 바로 Brand 팀으로 전환 → Brand 이미지 로딩 완료 → 직후 Web 이미지 응답 도착 → Brand 썸네일에 Web 이미지가 잠시 출력되는 문제
  • 모든 이미지가 외부 요청이었기 때문에, 로딩 완료 순서가 보장되지 않아 생긴 문제

3. 해결: 로컬 번들 + WebP/MP4 최적화

방향 전환: 모든 리소스를 src/assets에 포함

  • 이미지·영상을 프로젝트 안에 직접 포함하고, 빌드 시 assets로 번들
  • Vite의 import.meta.glob + eager: true 옵션으로 모든 파일을 미리 불러오는 유틸리티 구현
// src/utils/teamThumbnailImages.ts const beforeImageModules = import.meta.glob( "/src/assets/team_thumbnail/*_Before.webp", { eager: true, import: "default" } ); const afterImageModules = import.meta.glob( "/src/assets/team_thumbnail/*_After.webp", { eager: true, import: "default" } ); Object.entries(beforeImageModules).forEach(([path, url]) => { const teamKey = path.split("/").pop()?.replace("_Before.webp", ""); if (teamKey) beforeImageMap[teamKey] = url as string; });
  • getTeamThumbnailImage, getTeamAfterImage, getLocalPersonImages 등 모든 컴포넌트에서 로컬 경로 사용
  • import.meta.glob를 통해 빌드 시 모든 리소스를 미리 번들에 포함
  • TeamSelect, DesignerCard, Inter 등 모든 컴포넌트가 네트워크 요청 없이 즉시 이미지 활용 가능

리소스 최적화

  • WebP 변환: PNG/JPEG 대비 약 25~35% 용량 감소, 시각적 품질 유지
  • GIF → MP4 변환: 영상은 MP4로 변환하여 용량 다이어트 및 부드러운 재생
  • 빌드 결과물 용량 증가 문제는 이미지 포맷 최적화로 최대한 상쇄

4. 호버 애니메이션 안정화

// src/Pages/TeamSelect/index.tsx <TeamImageContainer onHover > <TeamImage src={getTeamThumbnailImage(selectedKey)} $isVisible={!isHovered} /> <TeamImage src={getTeamAfterImage(selectedKey)} // 로컬 경로, 즉시 표시 $isVisible={isHovered} /> </TeamImageContainer>
  • 모든 이미지가 빌드 시 포함되어 브라우저에 즉시 로드
  • onMouseEnter / onMouseLeave에 즉시 반응
  • 로딩 지연으로 인한 잘못된 이미지 표시 현상 해결

5. 결과: 전시 환경에 적합한 구조

항목Firebase Storage 방식로컬 번들 방식
초기 로딩느림 (매 요청 외부 네트워크)빠름 (한 번 로드 후 캐시)
호버 반응지연 발생, 오류 가능즉시 반응, 일관된 동작
네트워크 의존성높음빌드에 포함되면 거의 없음
리소스 관리저장소에서 교체 필요빌드 시 포함 (재빌드 필요)
전시장 적합성불안정 (네트워크 상황 영향)안정적 (NAS 배포 + 로컬)

현업에서는 보통 모든 이미지를 빌드에 포함하지 않습니다. 대신 다음과 같은 최적화를 사용합니다:

- CDN 활용: CloudFront, Cloudflare 등으로 전역 배포와 캐싱

- Lazy Loading: IntersectionObserver나 loading="lazy"로 뷰포트 진입 시 로드

- Responsive Images: srcset, sizes로 디바이스/해상도별 최적 이미지 제공

- 서버 사이드 최적화: Next.js Image 컴포넌트처럼 자동 리사이징/포맷 변환

- Progressive Loading: 저해상도 → 고해상도 단계적 로드

- 이미지 포맷 최적화: WebP, AVIF 등으로 용량 절감

- 동적 리사이징: 요청 시 필요한 크기로 변환해 제공

그러나 이 프로젝트에서는 모든 이미지를 빌드에 포함했습니다. 그 이유는 다음과 같습니다:

1. 전시장 환경의 특성

  • 오프라인 대비가 필요하고, 네트워크가 불안정할 수 있음
  • 외부 서버 의존을 줄여 안정성을 확보

2. 이미지 규모가 제한적

  • 이미지 약 70개, 그러나 이미지 post 기능 없음. 추가될 상황 x
  • WebP/MP4 최적화로 용량 증가가 허용 범위 내

3. 즉각적인 UX 요구

  • 호버 애니메이션이 즉시 반응해야 함
  • 로딩 지연으로 인한 잘못된 이미지 표시를 방지

4. NAS 배포 환경

  • 단순 정적 파일 배포 환경
  • CDN이나 동적 최적화 서버 구성이 어려움

5. 빌드 파일 크기와 트레이드오프

  • 빌드 결과물이 커지지만, 초기 로딩 후 모든 리소스가 캐시됨
  • 전시 기간 동안 이미지 변경이 거의 없어 재빌드 부담이 적음
  • 사용자 경험의 안정성과 속도가 용량 증가보다 우선

결론적으로, 이 프로젝트는 전시 환경의 제약과 요구사항을 고려해 “모든 리소스를 빌드에 포함”하는 방식을 선택했습니다. 현업의 일반적인 최적화 패턴과는 다르지만, 프로젝트 특성에 맞는 합리적인 선택이었습니다.

결국 “모든 리소스를 로컬에 포함한 뒤, 빌드 결과물을 NAS에 업로드”하는 방식이 전시 환경에 가장 안정적이고 빠른 경험을 제공했습니다. WebP/MP4 최적화로 용량도 충분히 확보했고, 호버 애니메이션 등 UI 반응도 기대한 대로 동작합니다.

이 과정을 통해, 전시 프로젝트와 같이 환경 제약이 뚜렷한 상황에서는 네트워크 의존성을 낮추고 유저 경험을 안정적으로 유지하는 전략이 더 중요하다는 것을 확인했습니다.