Vite + SVG 아이콘 트러블슈팅 회고

error-606·2025년 9월 1일

TIL

목록 보기
3/3

아이콘(SVG → React 컴포넌트) 렌더링 실패 트러블슈팅

1. 개요 (Summary)

Vite + React + vite-plugin-svgr 환경에서 SVG 아이콘이 React 컴포넌트로 렌더링되지 않고

  • JSX 사용 시 JSX 요소 형식 'X'에 구문 또는 호출 시그니처가 없습니다.ts(2604) 타입 오류
  • 자동 ICONS 맵이 비거나 “등록 불가” 경고
    가 발생했다.
    문제는 단일 원인이 아니라, “설정/타입/로직/캐시” 4가지 축이 복합적으로 얽혀 진단을 어렵게 만들었다.

2. 최초 증상 (Initial Symptoms)

관찰상세
타입 오류<ArchiveUp /> 사용 시 TS2604 (함수형 컴포넌트로 인식되지 않음)
런타임 500특정 시점에 Icons.ts 요청이 500 (내부 변환 중 예외)
ICONS 맵 비었음자동 수집된 ICONS 객체에 키가 없음 또는 경고 다수
typeof 결과typeof ArchiveUp === 'string' (URL) 로 출력되는 경우 발생
ReactComponent 네임드 export기대했던 ReactComponent 키 부재

3. 초기 가설 (Hypotheses Considered)

번호가설근거우선순위실제 여부
H1svgr 플러그인 설정/순서 문제typeof 가 string, ReactComponent 없음높음일부 (원인 구성요소)
H2svg 타입 선언(.d.ts) 누락/오염TS2604 / string 추론높음실제
H3ICONS 빌드 로직 결함맵 비었음 / 경고중간실제
H4캐시(node_modules/.vite) 영향설정 바꿔도 변화 없음중간실제(지연 요인)
H5파일 인코딩/주석 깨짐 영향한글 깨짐(�)낮음부차적 (주 원인 아님)
H6순환 의존성맵 비거나 undefined낮음최종 미확인 (가능성 낮음)
H7JSX/TSX 확장자 문제.ts 에 JSX 사용 시낮음이 케이스에서는 해당 없음

4. 시도했던 해결 단계 & 결과 (Experiments)

실험조치기대실제 결과판정
E1단일 아이콘 직접 import (import ArchiveUp from ...)함수형 컴포넌트 확인typeof 가 string 인 케이스 존재원인 계속
E2ReactComponent 네임드 import 시도네임드 export 존재 확인undefined / 키 부재svgr 미적용/순서 의심 강화
E3ICONS 생성 루프에서 mod.ReactComponent 만 체크정상 등록비거나 경고default 처리 필요 확인
E4루프에 Object.keys(mod) 로그 추가모듈 구조 파악keys: "default"svgr 또는 순서 문제 확증
E5svgr 플러그인 옵션 추가 (exportType: 'named')ReactComponent 제공타입 오류 지속타입 선언 오염 또는 순서 문제 남음
E6vite.config.ts 에 선언 유지한 채 캐시 삭제타입 반영 기대변화 없음선언 위치 자체 문제
E7declare module 들을 별도 svg.d.ts 로 이동타입 인식 정상화TS2604 감소 (일부 여전)절반 해결
E8플러그인 순서 react() → svgr()svgr() → react() 변경변환 성공ReactComponent 인식, typeof function핵심 조합 해결
E9ICONS 로직에 default 함수 fallback 추가맵 채워짐키 목록 채워짐성공
E10node_modules/.vite 삭제 후 재기동이전 캐시 무효화변환/타입 일관잔여 이상 증상 제거

5. 근본 원인 (Root Cause Analysis)

상세
타입 선언(Declaration Pollution)전역 타입이어야 할 declare module '*.svg' 블록을 vite.config.ts 내부에 배치 → TS 서버가 이를 안정적으로 전역 인식하지 못하거나 잘못된 string 추론 고착.
플러그인 순서(Transformation Order)react()svgr() 앞에 위치 → SVG 가 JSX 로 바뀌기 전에 React 플러그인의 JSX 처리가 지나가 변환 결과가 URL 로 남음.
ICONS 생성 로직(Logic Gap)ReactComponent 만 검사하고 default 함수/URL fallback 고려 부족 → 변환 성공 사례도 맵에 미등록.
빌드 캐시(Cache Persistence).vite 캐시가 이전 asset 처리 방식을 유지 → 설정 수정 후 즉시 효과 확인 실패(오판 유발).

이 네 요소가 동시에 작용하며 “한 부분을 고쳐도 바로 해결되지 않는” 착시를 만들었다.


6. 최종 해결 (Resolution)

조치설명이유
1. declare module 블록 제거 (vite.config.ts)타입 선언을 전용 .d.ts 파일로 이동안정적 전역 타입 공급
2. src/types/svg.d.ts 생성네임드 + URL 동시 선언TS 컴파일러에 정확한 구조 명시
3. 플러그인 순서 정리: svgr()react()SVG → JSX → React 처리 정상화변환 누락 방지
4. ICONS 생성 로직 개선 (ReactComponentdefault 함수)
5. 캐시 삭제: rm -rf node_modules/.vite구 버전 transform 버퍼 제거설정 반영 보장
6. 콘솔/typeof 진단 로그 도입반복 진단 비용 감소재발 시 즉시 원인 파악

7. 변경 전/후 주요 코드 비교

vite.config.ts (Before - 문제)

plugins: [
  react(),
  svgr({ svgrOptions: { exportType: 'named' }})
];
// (하단에 declare module *.svg ... 혼재)

vite.config.ts (After - 정상)

import svgr from 'vite-plugin-svgr';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    svgr({
      include: '**/*.svg',
      svgrOptions: { exportType: 'named' },
    }),
    react(),
  ],
});

svg.d.ts (새로 추가)

declare module '*.svg' {
  import * as React from 'react';
  export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

ICONS 맵 (Before - 축약)

if (mod.ReactComponent) ICONS[name] = mod.ReactComponent;
else console.warn('등록 불가');

ICONS 맵 (After)

const comp =
  (typeof mod.ReactComponent === 'function' && mod.ReactComponent) ||
  (typeof mod.default === 'function' && mod.default);

if (comp) ICONS[name] = comp;

8. 재발 방지 (Preventive Actions)

분류액션상태
문서화README 에 플러그인 순서/타입 선언 위치 규칙 추가필요
자동화pre-commit 스크립트: vite.config.ts 내 declare module 패턴 탐지고려
테스트간단 smoke test: typeof ReactComponent === 'function' 검사적용 가능
로깅ICONS 빌드 시 DEV 환경에서 keys 출력적용
교육온보딩 체크리스트에 “svg.d.ts 위치”, “캐시 삭제 단계” 추가필요
옵션svgr 플러그인에 enforce: 'pre' 명시권장

9. 빠른 진단 체커 (Runbook)

순서질문기대아니면?
1typeof ImportedIcon ?functionstring → 플러그인 순서/캐시
2hover 타입?React.FCstring → svg.d.ts 또는 선언 오염
3Object.keys(mod) 에 ReactComponent?포함미포함 → svgr 미작동
4ICONS keys 길이 > 0?yes0 → 생성 로직/경로/패턴
5캐시 삭제했나?yesno → .vite 삭제 후 재시도
6vite.config.ts 안에 declare module?no제거
7플러그인 순서?svgr → react역순이면 교체

10. 최종 요약 (Executive TL;DR)

SVG 아이콘이 렌더링되지 않은 이유는
1) 전역 타입 선언 오용(vite.config.ts 내부)
2) svgr 플러그인 순서 오류
3) ICONS 빌드 로직의 불완전한 분기
4) 캐시가 변화 반영을 지연
이 복합적으로 쌓여 “단일 원인 추적”을 방해했기 때문이다.
표준 위치의 .d.ts + 올바른 플러그인 순서 + 포괄적 ICONS 로직 + 캐시 무효화로 해결되었고, 재발 방지용 체크리스트/문서화가 필요하다.


11. 추가 개선 아이디어 (Future Enhancements)

아이디어기대 효과
아이콘 Lazy Load (import.meta.glob with dynamic import)초기 번들 크기 감소
Wrapper 컴포넌트 (size/color 통일)스타일 일관성
빌드 전 아이콘 validate 스크립트잘못된 SVG(속성/네임스페이스) 사전 차단
Storybook 자동 목록디자이너/개발 협업 효율

12. Action Items (Next Steps)

  • README: “SVG 처리 규칙” 섹션 작성
  • pre-commit grep: vite.config.tsdeclare module 금지
  • scripts/check-icons.ts 작성 (수량/변환 검증)
  • Wrapper 설계 (공통 size, color)
  • Lazy load 필요성 번들 분석으로 판단

13. 교훈 (Lessons Learned)

  • “한 번 고친 설정이 바로 안 먹는다” = 캐시 or 다중 원인 신호.
  • 타입 선언은 항상 전용 .d.ts 로; 실행 config 파일과 혼합 금지.
  • 플러그인 순서가 결과물의 데이터 “형” 자체를 바꿀 수 있다.
  • 진단 로그( keys, typeof )는 비용이 거의 없는데, 탐색 시간을 크게 절감한다.
  • 문제를 분리(단일 아이콘 → 자동 맵 → 전체 빌드)해 단계별로 축소하면 복합 오류도 빠르게 수렴 가능.
profile
프론트엔드 연습생

0개의 댓글