뤼튼에 검색한 결과 웹 페이지에서 아이콘을 불러올 때 너무 많은 네트워크 요청을 하게 되면 다음과 같은 문제점들이 발생할 수 있다고 한다.
- 페이지 로딩 속도 저하: 각 아이콘 파일을 불러오기 위한 네트워크 요청이 많아질수록, 웹 페이지의 로딩 시간이 길어질 수 있습니다. 이는 사용자 경험에 부정적인 영향을 미칠 수 있으며, 특히 모바일 사용자나 느린 인터넷 연결을 사용하는 사용자에게 더 큰 문제가 될 수 있습니다.
- 서버 부하 증가: 각 사용자가 페이지에 접속할 때마다 많은 수의 파일을 요청하게 되면, 서버에 부하가 증가하여 성능 저하를 초래할 수 있습니다. 이는 트래픽이 많은 사이트에서 특히 문제가 될 수 있습니다.
- 브라우저 동시 연결 한계: 브라우저는 동시에 일정 수의 HTTP 요청만 처리할 수 있으며, 이 한계를 초과하는 요청은 대기 상태로 들어가게 됩니다. 너무 많은 아이콘 파일을 불러오려고 하면 이러한 한계에 도달해 다른 중요한 요소의 로딩이 지연될 수 있습니다.
- 데이터 사용량 증가: 모바일 사용자의 경우 네트워크 요청이 많아질수록 데이터 사용량이 증가하게 됩니다. 이는 데이터 제한이 있는 사용자에게 추가 비용이나 속도 제한을 초래할 수 있습니다.
- SEO 영향: 웹 페이지의 로딩 속도는 검색 엔진 최적화(SEO)에 중요한 요소 중 하나입니다. 페이지 로딩 속도가 느려지면 검색 엔진 순위에 부정적인 영향을 미칠 수 있습니다.
이미지 스프라이트(image sprite)란 여러 개의 이미지를 하나의 이미지로 합쳐서 관리하는 이미지를 의미합니다.
CSS 이미지 스프라이트
SVG 파일 내에 여러 개의 그래픽 요소(예: 아이콘)를 포함시키고, 필요한 부분만을 태그를 통해 재사용하는 기술입니다.
SVG Sprite 기법을 사용해 나만의 특별한 Icon 컴포넌트 개발
SVG 스프라이트 기법으로 사이트 성능 향상시키기(리액트에서 스프라이트 SVG 사용하기)
이 블로그 글에서 SVG Sprite를 적용하는 방법에 대해 잘 설명이 돼 있어서 그대로 따라 했다.
sybol
태그의 id
를 조합해서 특정 아이콘을 분리한다.// \src\constants\reactQuillIcons.ts
import reactQuillSvgSprite from "@/assets/svgSprite/reactQuillIcons.svg";
export const QUILL_ICONS = {
header1: {
src: `${reactQuillSvgSprite}#header1`,
alt: "제목1 아이콘",
},
header2: {
src: `${reactQuillSvgSprite}#header2`,
alt: "제목2 아이콘",
},
//중략
link: {
src: `${reactQuillSvgSprite}#link`,
alt: "링크 아이콘",
},
image: {
src: `${reactQuillSvgSprite}#image`,
alt: "이미지 아이콘",
},
};
<use>
태그를 이용해서 SVG Sprite의 요소를 참조한다.// \src\components\commons\ReactQuill\CustomToolbar.tsx
import styled from "styled-components";
import DESIGN_TOKEN from "@/styles/tokens";
import { QUILL_ICONS } from "@/constants/reactQuillIcons";
import { Quill } from "react-quill";
const { color, mediaQueries } = DESIGN_TOKEN;
const icons = Quill.import("ui/icons");
icons["header"]["1"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.header1.src} /></svg>`;
icons["header"]["2"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.header2.src} /></svg>`;
icons["header"]["3"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.header3.src} /></svg>`;
icons["blockquote"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.header1.src} /></svg>`;
icons["bold"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.bold.src} /></svg>`;
icons["italic"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.italic.src} /></svg>`;
icons["underline"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.underline.src} /></svg>`;
icons["image"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.image.src} /></svg>`;
icons["link"] = `<svg class="fm_editor_icon"><use href=${QUILL_ICONS.link.src} /></svg>`;
//중략
인라인 SVG는 HTML에 직접
<svg>
태그를 사용하는 것을 말합니다.
SVG Security
기술 스택 아이콘에도 위와 같은 방법으로 svg sprite 파일을 사용하려고 했으나 예상치 못한 문제가 생겨서 다른 방법(인라인 SVG Sprite)을 찾게 됐다.
<symbol>
요소 내부에<linearGradient>
를 포함하고 있다는 것이었다.=> 이 문제가 chrome 브러우저 때문일 수 있다고 추측했다.
이 문제의 정확한 원인을 찾고 싶었으나 찾지 못해서 뤼튼에 검색해본 결과 다음과 같은 답변을 얻었다.
SVG 내에 정의된
<linearGradient>
요소는 SVG 스프라이트와 같이 외부 파일에 정의되어 있고,<use>
태그를 통해 참조될 때, 일부 브라우저에서는 정상적으로 렌더링되지 않을 수 있습니다. 이는 주로<use>
태그가 외부의 SVG 스프라이트에서 정의된 gradient, mask, clip-path 같은 요소들을 참조할 때 발생하는 보안 제한 때문입니다.브라우저의 보안 모델은 다른 도메인 또는 동일 도메인에서조차도 외부 SVG 파일의 특정 요소(예:
<linearGradient>
,<mask>
,<clipPath>
)에 대한 참조를 제한하기 때문에 이런 방식에 문제가 발생할 수 있습니다.
뤼튼에서 나온 내용을 검증하기 위해 자료를 찾아 보았지만 문서로 된 정확한 근거는 찾지 못했다. 깃헙 이슈에서 관련 문제로 토론한 내용을 보니 chrome 문제인 것 같았다.
svg <use>
tag frequently breaks on redraw in Chrome
SVG Sprite 기법을 사용해 나만의 특별한 Icon 컴포넌트 개발
SvgIcon 컴퍼넌트: svg sprite html에 임베드 방식
위 두개의 블로그에 설명히 자세히 나와있었다.
createPortal
함수를 이용해서 DOM에 접근해 body에 삽입하는 컴포넌트를 만든다. // \src\components\commons\GlobalStackSvgSprite.tsx
import { createPortal } from "react-dom";
const stackSvgSpriteCode = (
<svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<symbol id="aws" viewBox="0 0 256 153">
<path
fill="#252F3E"
d="M72.392 55.438c0 3.137.34 5.68.933
// 중략
</svg>
);
export function GlobalStackSvgSprite() {
return createPortal(stackSvgSpriteCode, document.body);
}
xmlns:xlink
속성을 xmlnsXlink
로 변경한다.<linearGradient>
태그에 중복되는 id가 있다면 고유한 id로 변경한다.// \src\App.tsx
// 중략
function App() {
return (
<>
<QueryClientProvider client={queryClient}>
<GlobalStyles />
<ReactQueryDevtools initialIsOpen={false} />
<PageRouter />
<GlobalStackSvgSprite /> // 컴포넌트를 여기에 삽입
<Toasts />
</QueryClientProvider>
</>
);
}
export default App;
<use>
태그와 SVG Sprite의 symbol
태그의 id
를 이용해서 아이콘을 불러오기<use>
태그를 사용하여 필요한 아이콘을 SVG Sprite로부터 불러온다. 이때 symbol 태그의 id 값을 사용한다.// \src\components\commons\Stack.tsx
//중략
export default function Stack({ stack, className }: StackProps) {
const icon: Image =
stack && stack.img
? {
src: stack.img,
alt: stack.name,
}
: ICONS.questionMark;
return (
<Background className={className}>
<Icon>
<use href={icon.src} /> // icon.src 예시) "#javascript"
</Icon>
</Background>
);
}
//중략
const Icon = styled.svg`
width: 100%; /* 컨테이너 너비에 맞춤 */
height: 100%; /* 컨테이너 높이에 맞춤 */
object-fit: contain;
`;
<img>
태그를 <svg>
태그와 <use>
태그로 일일이 변경해야 하는 번거로움이 있음을 깨달았다. 이를 해결하기 위해, 아이콘을 쉽게 최적화하고 관리할 수 있는 별도의 컴포넌트를 제작하는 방안을 고려하게 되었다.