React) SVG 스프라이트와 개별 SVG 아이콘 사용에 관한 개발 이야기
SVG 스프라이트와 개별 SVG 아이콘 사용에 관한 개발 이야기
들어가며
개발 과정에서 우리는 종종 당연하게 여기는 패턴이나 방식들이 있습니다. 그 중 하나가 아이콘을 효율적으로 관리하기 위한 '스프라이트(Sprite)' 기법인데요. 최근 SNS 연동 기능을 구현하면서 이 접근 방식에 대해 다시 한번 생각해보게 되었습니다.
문제 상황: 아이콘 관리
SNS 로그인 기능을 개발하면서 다양한 서비스의 로고들을 한 페이지에서 불러와야 했습니다. 네이버, 인스타, 틱톡, 페이스북 등 약 10개의 로고를 효율적으로 처리해야 했죠. 처음 고민했던 것은 무엇보다 '네트워크 요청 수를 줄이는 것'이었습니다.
첫 접근: PNG 스프라이트
전통적인 웹 최적화 기법으로, 여러 이미지를 하나의 큰 이미지 파일로 합친 후 CSS의 background-position을 사용해 필요한 부분만 잘라서 보여주는 PNG 스프라이트를 고려했습니다. 이 방식은 HTTP 요청 수를 크게 줄일 수 있다는 장점이 있습니다.
.icon-naver {
background-image: url('/sprites.png');
background-position: 0 0;
width: 24px;
height: 24px;
}
.icon-kakao {
background-image: url('/sprites.png');
background-position: -24px 0;
width: 24px;
height: 24px;
}
ChatGPT의 조언: SVG 사용
이 접근 방식에 대해 ChatGPT에 물어봤을 때, PNG보다는 SVG가 더 좋을 것이라는 조언을 받았습니다. SVG는 벡터 기반이라 어떤 크기로도 깔끔하게 표시되고, 파일 크기도 작을 수 있기 때문이죠.
SVG 스프라이트 시도
SVG도 스프라이트로 만들 수 있습니다. Vite 프로젝트에서는 vite-plugin-svg-icons 같은 플러그인을 사용해 구현할 수 있습니다:
// vite.config.js
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
export default {
plugins: [
react(),
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), "src/assets/icons")],
symbolId: "icon-[name]",
svgoOptions: true,
inject: "body-last",
customDomId: "svg-sprite",
}),
],
};
그리고 아이콘을 사용하는 컴포넌트를 만들었습니다:
// Icon.jsx
type IconProps = {
name: string;
size?: number;
color?: string;
className?: string;
};
export const Icon = ({
name,
size = 24,
color = "currentColor",
className = "",
}: IconProps) => (
<svg width={size} height={size} fill={color} className={`${className}`}>
<use xlinkHref={`#icon-${name}`} />
</svg>
);
export const Naver = () => (
<Icon name="naver" className="w-6 h-6 text-blue-500" />
);
다운로드 수: 41338
마지막 업데이트: 3년 전
깨달음: SVG는 스프라이트가 필요 없다
그런데 SVG 스프라이트를 구현하려고 하다가 중요한 사실을 깨달았습니다. SVG는 XML 기반 코드로, 직접 React 컴포넌트로 가져와서 사용할 수 있습니다. 스프라이트를 만들 필요가 없이, 각 SVG 파일을 직접 임포트하는 방식이 오히려 더 간단하고 관리하기 좋을 수 있습니다.
최종 선택: vite-plugin-svgr
결국 vite-plugin-svgr 패키지를 사용해 SVG 파일을 React 컴포넌트로 직접 임포트하는 방식을 선택했습니다:
vite-env.d.ts
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" /> // << 추가
vite.config.ts
import path from "path";
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react-swc";
import svgr from "vite-plugin-svgr"; // << 추가
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
return {
base: "/",
...
},
plugins: [react(), svgr()], // << 추가
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
};
});
NaverIcon.tsx
import NaverLogo from "@/assets/icons/naver.svg?react";
type IconProps = {
size?: number;
color?: string;
className?: string;
};
export const NaverIcon = ({
size = 24,
color = "currentColor",
className = "",
}: IconProps) => (
<NaverLogo width={size} height={size} color={color} className={className} />
);
이 방식의 장점은:
- 코드 가독성: 각 아이콘이 어떤 것인지 임포트 구문만 봐도 명확합니다
- 유지보수: 아이콘을 추가하거나 수정할 때 개별 파일만 관리하면 됩니다
- 타입 안정성: TypeScript와 함께 사용할 때 속성 타입 체크가 가능합니다
- 번들링 최적화: 현대 번들러는 SVG를 효율적으로 처리하며, 트리 쉐이킹으로 사용하지 않는 아이콘은 제외됩니다
다운로드 수: 1,742,398
마지막 업데이트: 6개월 전
네트워크 요청 수에 대한 오해
처음에는 "여러 아이콘을 개별적으로 불러오면 네트워크 요청 수가 늘어나지 않을까?"라는 걱정이 있었습니다. 하지만 이는 빌드 과정의 이해 부족에서 오는 오해였습니다.
현대 빌드 시스템(Webpack, Vite 등)에서는:
- SVG 파일을 임포트하면 빌드 시점에 JavaScript 코드로 변환됩니다
- 이 코드는 번들에 포함되어 별도의 네트워크 요청 없이 앱과 함께 로드됩니다
- 즉, 10개의 SVG를 임포트해도 추가 네트워크 요청은 발생하지 않습니다
개별로 불러오는 것이 사용 방법도 간편하고 네트워크 수 요청도 똑같고, 오히려 스프라이트로 했을 때 필요하지 않은 아이콘들도 초기에 불러와서 로딩 속도만 느려질 뿐이였습니다.
SVG 최적화: SVGO로 파일 크기 줄이기
개별 SVG 파일을 사용하는 접근 방식을 선택했지만, 한 가지 더 고려할 점이 있었습니다. 디자인 도구(예: Figma)에서 내보낸 SVG 파일은 종종 불필요한 코드를 포함하고 있어 파일 크기가 커지는 문제가 있습니다.
이 문제를 해결하기 위해 svgo 도구를 활용했습니다. SVGO(SVG Optimizer)는 SVG 파일에서 불필요한 마크업을 제거하고 다양한 최적화를 수행하는 강력한 도구입니다.
# SVGO 설치 및 실행
npx svgo -f src/assets/icons
직접 Figma에서 다운로드한 SVG 파일들에 이 최적화 도구를 적용해본 결과, 놀랍게도 일부 파일은 크기가 최대 20%까지 감소했습니다. 불필요한 메타데이터, 주석, 빈 그룹 등이 모두 제거되어 파일 크기가 크게 줄었습니다.
효율적인 워크플로우를 위해 package.json에 스크립트도 추가했습니다:
{
"scripts": {
"optimize-icons": "npx svgo -f src/assets/icons"
}
}
이제 npm run optimize-icons 명령어만으로 모든 아이콘을 한 번에 최적화할 수 있습니다. 새로운 아이콘이 추가될 때마다 이 스크립트를 실행하면 항상 최적화된 SVG 파일을 유지할 수 있습니다.
다운로드 수: 19,667,708
마지막 업데이트: 9일 전
결론
개발에서는 종종 "이렇게 하는 게 좋다"라는 관념에 사로잡히곤 합니다. 스프라이트가 항상 최적의 해결책이라고 생각했지만, 실제로는 기술과 도구에 맞는 더 간단하고 효율적인 접근 방식이 있었습니다.
SVG 아이콘을 관리할 때는 각 파일을 개별적으로 관리하고 컴포넌트로 임포트하는 방식이 현대 프론트엔드 개발 흐름에 더 적합할 수 있습니다. 여기에 SVGO 같은 최적화 도구를 함께 사용하면 성능까지 향상시킬 수 있습니다.
때로는 "당연히 이렇게 해야 한다"는 생각을 잠시 내려놓고, 현재의 도구와 환경에서 정말 최적의 방법이 무엇인지 다시 생각해보는 것이 중요하다고 생각이 들었습니다.