1. 아이콘 깜빡거리는 문제 개선
웹페이지를 개발하다 보면 아이콘이 렌더링될 때 깜빡이는 현상을 한 번쯤 경험하게 됩니다. 저도 최근에 이 문제로 고민했는데요, 네트워크 탭을 열어보니 아이콘 하나하나가 개별적으로 요청되면서 시간이 꽤 걸리고 있었습니다. 이 문제를 해결하기 위해 여러 방법을 시도해본 과정을 공유해보려고 합니다.
문제 상황 파악하기
먼저 정확히 무엇이 문제인지 파악해봤습니다. 페이지에 아이콘이 10개만 있어도 10번의 HTTP 요청이 발생하고, 각 요청마다 네트워크 레이턴시가 추가됩니다. 특히 모바일 환경이나 네트워크가 느린 상황에서는 이 문제가 더 두드러지게 나타나죠. 아이콘들이 하나씩 로딩되면서 깜빡이는 모습은 사용자 경험 측면에서도 좋지 않았습니다.
-
SVG 태그 직접 사용
첫 번째로 SVG 코드를 직접 컴포넌트에 넣는 방법을 생각해봤어요. 이렇게 하면 HTTP 요청이 없어지니까 깜빡임 문제는 확실히 해결됩니다. 하지만 실제로 적용해보니 JSX 코드가 너무 길어져서 가독성이 떨어지더라고요. 코드를 보면서 "이게 무슨 아이콘이지?" 하고 헷갈리는 경우가 많았습니다.
-
SVG 파일 Import
두 번째는 SVG 파일을 import해서 사용하는 방법이었습니다. Next.js나 Vite 같은 번들러에서는 SVG를 React 컴포넌트처럼 import할 수 있죠. 이 방법도 나쁘진 않은데, 이 방법은 아이콘이 JavaScript 번들에 포함되기 때문에, 아이콘이 많아질수록 JS 번들 사이즈가 불필요하게 커지는 문제가 있었습니다. 이는 브라우저가 JS를 해석하는 비용을 높여 초기 로딩 속도에 악영향을 줄 수 있죠. 또한 로직 수정으로 JS 파일이 변경될 때마다 아이콘 데이터까지 캐시가 만료되어 다시 다운로드해야 하는 캐싱 비효율도 발생합니다.
-
Sprite 파일 사용
마지막으로 SVG Sprite 기법을 떠올렸습니다. 프론트엔드 최적화 강의에서 배웠던 내용인데, 여러 개의 SVG를 하나의 파일로 합쳐서 한 번의 요청으로 모든 아이콘을 사용할 수 있는 방법입니다. 이게 가장 효율적일 것 같아서 직접 적용해보기로 했습니다.
// package.json
"make-sprite": "rm public/sprite/sprite.svg && svgstore -o public/sprite/sprite.svg public/sprite/icons/**/*.svg"
SVG Sprite 구현하기
Sprite 파일을 만들기 위해 svgstore 라이브러리를 사용했습니다. 먼저 package.json에 스크립트를 추가했어요.
{
"scripts": {
"make-sprite": "rm public/sprite/sprite.svg && svgstore -o public/sprite/sprite.svg public/sprite/icons/**/*.svg"
}
}
이 스크립트는 public/sprite/icons 폴더 안에 있는 모든 SVG 파일을 하나의 sprite.svg 파일로 합쳐줍니다. 기존 파일을 지우고 새로 생성하는 방식이라 항상 최신 상태를 유지할 수 있죠. yarn make-sprite 명령어만 실행하면 됩니다.
다음으로 재사용 가능한 Icon 컴포넌트를 만들었습니다.
// Icon.tsx
import type { SVGProps } from 'react';
interface IconProps extends SVGProps<SVGSVGElement> {
id: string;
}
export default function Icon({ id, width = 24, height = 24, fill = 'none', ...props }: IconProps) {
return (
<svg width={width} height={height} fill={fill} {...props}>
<use href={`/sprite/sprite.svg#${id}`} />
</svg>
);
}
이 컴포넌트의 핵심은 <use> 태그입니다. SVG의 <use> 태그는 다른 SVG 요소를 참조해서 재사용할 수 있게 해주는데요, 여기서는 sprite 파일 안에 있는 특정 아이콘을 id로 참조하고 있습니다. # 뒤에 붙는 id는 원본 SVG 파일명을 그대로 사용하면 됩니다.
IconProps는 기본 SVGProps를 확장해서 만들었기 때문에, className, onClick 같은 일반적인 SVG 속성들도 모두 사용할 수 있습니다. 타입 안정성도 확보되고, 개발 경험도 좋아지는 구조죠.
실제 사용할 때는 이렇게 간단하게 적용할 수 있습니다.
// 적용 전
<IconButton>
<Image src="/icons/24/navigate_next.svg" width={24} height={24} />
</IconButton>
// 적용 후
<IconButton>
<Icon id="24-navigate_next" />
</IconButton>
코드가 훨씬 깔끔해지고, 아이콘의 의도도 명확하게 드러납니다. 게다가 Image 컴포넌트를 거치지 않으니 불필요한 추상화 레이어도 줄어들었어요.
추가로 알아두면 좋은 팁들
실제로 적용하면서 몇 가지 더 알게 된 내용들이 있어서 공유해봅니다.
-
캐싱 전략
sprite.svg 파일은 한 번 로드되면 브라우저 캐시에 저장됩니다. 그래서 다른 페이지로 이동해도 다시 다운로드할 필요가 없어요. 다만 아이콘을 추가하거나 수정했을 때는 캐시 무효화를 위해 파일명에 해시를 추가하는 것도 고려해볼 만합니다.
-
접근성 고려사항
스크린 리더 사용자를 위해 아이콘에 적절한
aria-label을 추가하는 것이 좋습니다. 특히 아이콘만 있고 텍스트가 없는 버튼이라면 필수입니다.<Icon id="24-close" aria-label="닫기" /> -
색상 커스터마이징
SVG의
fill이나stroke속성을 props로 받아서 동적으로 색상을 변경할 수 있습니다. 다크 모드 대응할 때도 유용하죠.<Icon id="24-heart" fill="red" /> -
빌드 프로세스 자동화
매번 수동으로
yarn make-sprite를 실행하는 게 번거롭다면, pre-build 스크립트로 자동화할 수 있습니다. 또는 파일 변경 감지(watch mode)를 추가해서 개발할 때 자동으로 sprite 파일이 업데이트되도록 만들 수도 있습니다.
언제 이 방법을 사용하면 좋을까요?
모든 상황에 완벽한 해결책은 없듯이, Sprite 기법도 적합한 경우와 그렇지 않은 경우가 있습니다.
- Sprite 기법이 적합한 경우
- 페이지 전체에서 사용하는 공통 아이콘이 많을 때
- 아이콘 디자인이 단순하고 단색 또는 제한된 색상을 사용할 때
- 초기 로딩 성능이 중요한 서비스
- 네트워크 요청 수를 최소화해야 하는 환경
- 다른 방법을 고려해볼 경우
- 아이콘이 매우 적고 한두 페이지에서만 사용될 때 (오버엔지니어링일 수 있습니다)
- 복잡한 멀티컬러 일러스트레이션이 많을 때
- 페이지별로 완전히 다른 아이콘 세트를 사용할 때 (코드 스플리팅이 더 효과적일 수 있습니다)
마무리하며
아이콘 깜빡임이라는 작은 문제에서 시작했지만, 성능 최적화라는 더 큰 주제를 다뤄볼 수 있었습니다. SVG Sprite 기법은 오래된 기술이지만 여전히 효과적이고, 특히 아이콘이 많은 서비스에서는 확실한 성능 개선을 체감할 수 있습니다.
요즘은 Icon Font나 React Icon 라이브러리처럼 편리한 대안들도 많지만, 직접 구현한 Sprite 방식은 번들 크기를 최소화하고 정확히 필요한 아이콘만 사용할 수 있다는 장점이 있습니다. 프로젝트 특성에 맞는 방법을 선택하는 게 중요하겠죠.
혹시 여러분도 비슷한 문제를 겪고 계시다면, 이 방법을 한번 시도해보시길 추천합니다!