[ComitChu 개발기] Vite에서 로컬은 되는데 배포하면 이미지가 안 보이는 문제와 해결 방법

Suyo·2025년 8월 11일
0

ComitChu

목록 보기
5/5

1. 문제 상황

React + Vite 프로젝트를 개발하던 중, 다음과 같이 이미지 경로를 설정했다.

// ChuViewer.tsx (초기 코드)
const chuImagePath = `/src/assets/images/chu/happy/${mainChu.lang}.png`;
const backgroundImagePath = `/src/assets/images/backgrounds/${mainChu.background}.png`;
  • 개발 서버(localhost)에서는 이미지가 정상 표시됐다.
  • 프로덕션 빌드(배포)에서는 이미지가 표시되지 않고 404가 발생했다.

로컬에서는 됐지만, 배포 환경에서 경로가 깨지는 현상이 있었다.


2. 원인 분석

Vite 개발 서버는 /src/... 경로를 파일 시스템 기준으로 직접 매핑해줬다. 그래서 개발 중에는 위 코드가 동작했다.
그러나 빌드 시 Vite는 src/assets의 파일을 dist/assets로 복사하면서 해시 파일명으로 변환했다.

예시:

happy/en.png → happy/en.4a6c21ef.png

코드에 남아 있던 /src/... 경로는 빌드 후 실제 파일 위치와 일치하지 않았고, 그 결과 배포 환경에서 자산을 찾지 못했다.


3. 올바른 해결 방법 – new URL(..., import.meta.url)

동적으로 구성되는 자산 경로에는 Vite가 빌드 타임에 추적·치환할 수 있는 문법을 사용했다.
new URL(path, import.meta.url).href를 사용하니 빌드 시 해시가 반영된 실제 경로로 자동 변환됐다.

// 수정된 ChuViewer.tsx
const chuImagePath = new URL(
  `../../assets/images/chu/happy/${mainChu.lang}.png`,
  import.meta.url
).href;

const backgroundImagePath = new URL(
  `../../assets/images/backgrounds/${mainChu.background}.png`,
  import.meta.url
).href;

이렇게 수정하니:

  • 개발 서버에서는 원본 경로로 정상 표시됐다.
  • 프로덕션 빌드에서는 해시가 포함된 파일 경로로 자동 치환되어 문제없이 표시됐다.

4. 또 다른 방법 – import.meta.glob로 폴더 일괄 매핑

여러 개의 이미지를 조건에 따라 불러와야 하는 경우, import.meta.glob를 사용하면 폴더 전체를 객체 형태로 매핑할 수 있었다.
이 방법은 반복적인 new URL 작성 없이도 모든 이미지를 한 번에 불러올 수 있었다.

// chu 폴더 내 모든 이미지 매핑
const chuImages = import.meta.glob('../../assets/images/chu/happy/*.png', {
  eager: true,
  import: 'default',
});

// backgrounds 폴더 내 모든 이미지 매핑
const backgroundImages = import.meta.glob('../../assets/images/backgrounds/*.png', {
  eager: true,
  import: 'default',
});

// 사용 예시
const chuImagePath =
  chuImages[`../../assets/images/chu/happy/${mainChu.lang}.png`];
const backgroundImagePath =
  backgroundImages[`../../assets/images/backgrounds/${mainChu.background}.png`];
  • eager: true로 즉시 로드되도록 했다.
  • import: 'default'로 URL 문자열만 가져오도록 했다.
  • 빌드 시 해시가 반영된 실제 경로로 자동 치환되어, 로컬과 배포 환경 모두에서 안전하게 동작했다.

5. 선택 가이드 – new URL vs import.meta.glob

구분new URL(..., import.meta.url)import.meta.glob
쓰임새개별 파일 경로를 동적으로 구성할 때폴더 전체를 일괄 매핑해 키로 접근할 때
코드 양파일마다 1줄씩 선언폴더당 1번 선언 후 재사용
타입 안전성문자열 경로 조합 중심매핑된 키 집합이 고정돼 비교적 안전
빌드 추적명시적 URL로 확실히 추적됨글로브 패턴으로 폴더 단위 추적
런타임 비용매우 낮음eager 사용 시 초기에 메모리 로드 증가 가능
지연 로드기본 없음eager: false로 분할/지연 로드 가능(동적 import)
유지보수소량 자산에 단순다수 자산, 스킨/테마처럼 목록 접근에 유리

권장 기준

  • 파일 수가 적고, 경로가 간단히 결정된다면 → new URL이 가장 직관적이다.
  • 스킨/로케일/테마처럼 “목록에서 선택”하는 패턴이라면 → import.meta.glob가 깔끔하다.
  • 번들 크기/초기 로드 최적화가 중요하다면 → import.meta.glob에서 eager: false로 코드 스플리팅을 활용한다.
// 지연 로드(코드 스플리팅) 예시
const chuImages = import.meta.glob('../../assets/images/chu/happy/*.png'); // eager 미사용

async function getChuImage(lang: string) {
  const loader = chuImages[`../../assets/images/chu/happy/${lang}.png`];
  if (!loader) return null;
  const mod = await loader();           // 동적 import
  return mod.default as string;         // 해시 반영된 URL
}

마무리

  • /src/... 직접 경로 표기는 개발 서버에서만 우연히 동작했다.
  • 빌드 후에는 자산 파일명이 해시로 변경되므로, 빌드 타임에 치환 가능한 방식이 필요했다.
  • 동적 이미지 경로new URL(..., import.meta.url).href
  • 여러 이미지 일괄 로드import.meta.glob
  • 지연 로드/코드 스플리팅이 필요하면 import.meta.glob에서 eager를 생략해 동적 import로 처리했다.
profile
Mee-

0개의 댓글