iOS Safari에서만 SVG 아이콘이 사라졌던 디버깅 기록
2025-12-29상품 카드 리스트에서 Pick 아이콘과 별점 아이콘을 SVG로 사용하고 있었는데, iOS에서만 아주 이상한 현상이 발생했습니다. 첫 번째 카드의 아이콘은 잘 보이는데, 두 번째 카드부터는 동일한 아이콘이 통째로 보이지 않았습니다.
이 글은 그 트러블슈팅 과정과, 최종적으로 왜 clipPath를 제거하는 것이 해답이 되었는지 정리한 내용입니다.
문제 상황
증상
상품 리스트 페이지에서 다음과 같은 현상이 발생했습니다:
- 첫 페이지 진입 시 모든 아이콘은 정상 노출
- 하지만 다른 페이지로 이동했다가 다시 돌아왔을 때 첫번째 카드 컴포넌트를 제외하고 다른 컬포넌트의 SVG 아이콘이 노출되지 않았습니다.
개발자 도구로 확인한 결과
Chrome DevTools로 DOM과 스타일을 확인했을 때:
<svg>요소는 DOM에 존재합니다width,height,display,opacity,fill모두 정상입니다- 첫 번째 카드와 나머지 카드의 CSS 차이가 없습니다
- 하지만 화면에는 보이지 않습니다
재현 환경
- iOS Safari (15+)
- iOS WebView
- Desktop Chrome (재현 안 됨)
- Desktop Safari (재현 안 됨)
- Android Chrome (재현 안 됨)
문제의 SVG 구조
문제가 되었던 StarIcon의 원본 SVG 코드입니다:
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3200_4527)">
<path d="..." fill="#ECEEEF"/>
</g>
<defs>
<clipPath id="clip0_3200_4527">
<rect width="11" height="11" fill="white" transform="translate(0.183594)"/>
</clipPath>
</defs>
</svg>
원인
문제의 원인은 생각보다 간단했습니다. 바로 SVG의 구성에 문제가 있었죠
바로 <clipPath id="clip0_3200_4527"> 때문입니다.
결과부터 얘기하면, SVG 컴포넌트를 카드마다 반복해서 렌더링하면, DOM 안에 동일한 id="clip0_3200_4527"를 가진 <clipPath>가 여러 번 등장하게 되고 이게 아이콘이 사라지는 원인입니다.
SVG의 id 참조 메커니즘
React에서 배열을 렌더링할떄 저희는 항상 key를 지정해줍니다.
이 key는 유니크해야하고 그렇지 않으면 UI상 문제가 발생하죠
SVG도 똑같이 id는 문서 전체에서 유일해야 합니다.
하지만 문제가 된 컴포넌트에선 SVG 파일을 여러 번 inline으로 사용하는 경우가 많고, 이때 자연스럽게 id가 중복된 상태가 됩니다.
// 이렇게 사용하면
{products.map(product => (
<ProductCard key={product.id}>
<StarIcon /> {/* 각각 동일한 id를 가진 clipPath 포함 */}
</ProductCard>
))}
// DOM에는 이렇게 됩니다
<svg>
<defs><clipPath id="clip0_3200_4527">...</clipPath></defs>
...
</svg>
<svg>
<defs><clipPath id="clip0_3200_4527">...</clipPath></defs> <!-- 중복! -->
...
</svg>
<svg>
<defs><clipPath id="clip0_3200_4527">...</clipPath></defs> <!-- 중복! -->
...
</svg>
브라우저별 처리 차이
대부분의 브라우저는 이 상황을 어느 정도 관대하게 처리해서 시각적으로 문제가 잘 드러나지 않습니다.
하지만 WebKit(Safari 엔진)은 SVG 내부 id를 전역 단위로 처리하는 경향이 있습니다.
때문에 DOM 복원 시 참조를 제대로 이어붙이지 못하는 버그가 있고, 특히 BFCache 복원 과정에서 문제가 자주 발생합니다
BFCache란?
BFCache는 브라우저가 페이지를 메모리에 저장해두고, 뒤로 가기/앞으로 가기 시 빠르게 복원하는 기능입니다.
iOS Safari는 이 기능을 적극적으로 사용하는데, 페이지를 완전히 새로 렌더링하는 것이 아니라 스냅샷을 복원합니다.
문제가 발생하는 과정
1. 첫 진입 시
각 카드의 SVG 컴포넌트가 렌더링됨
→ 각 <svg> 안의 <defs>, <clipPath>가 DOM에 붙음
→ clip-path="url(#clip0_3200_4527)"가 적절한 <clipPath>를 찾아감
→ 정상 렌더링됨
2. 다른 페이지로 이동
현재 페이지가 BFCache에 스냅샷으로 저장됨
→ DOM 상태가 메모리에 보존됨
3. 뒤로 가기로 돌아올 때
BFCache에 있던 DOM이 복원됨
→ WebKit이 inline SVG의 <defs>와 id 참조를 완벽하게 복원하지 못함
→ 동일한 id를 가진 <clipPath>가 여러 개 있을 때
- 어떤 <clipPath>에 매칭할지 애매해짐
- 첫 번째 인스턴스는 정상 작동
- 나머지는 참조가 끊기거나 잘못된 clipPath를 참조
→ 결과적으로 렌더링이 화면 밖으로 잘려나감
이런 문제 때문에 다시 돌아온 페이지에선 SVG 아이콘이 렌더링되지 않았죠
시도했던 해결책들
1차 시도: SVGR + SVGO 설정
처음엔 GPT 도움을 받아 빌드 타임에 id 충돌을 줄이기 위해 SVGR 설정을 조정했습니다:
// next.config.js
webpack(config, { isServer }) {
config.module.rules.push({
test: /\.svg$/i,
issuer: { and: [/\.(js|ts)x?$/] },
use: [
{
loader: "@svgr/webpack",
options: {
svgo: true,
svgoConfig: {
plugins: [
"preset-default",
"prefixIds", // id에 prefix를 붙여 충돌 방지
],
},
},
},
],
});
if (!isServer) {
config.resolve.fallback = {
fs: false,
dns: false,
net: false,
tls: false,
http2: false,
dgram: false,
};
}
return config;
}
하지만 위 방법을 사용해도 iOS에서 뒤로 가기 이후 SVG가 사라지는 문제는 그대로 재현되었습니다
id를 변형하거나 prefix를 붙이는 것만으로는 iOS Safari의 BFCache 복원 버그를 피할 수 없었습니다
최종 해결: clipPath 제거
근본적인 원인을 해결하기 위해 SVG 구조를 다시 분석했습니다
viewBox="0 0 12 11"
clipPath: <rect width="11" height="11" transform="translate(0.183594)"/>
path: 대부분 viewBox 내부에 위치
해당 아이콘을 자세히 살펴보면 clipPath는 사실상 유의미한 역할을 하지 않았습니다.
path 좌표가 이미 viewBox 내부에 있었고 일부 음수 좌표가 있지만 화면 밖이라 제거해도 보이는 변화가 없습니다. 또한clipPath를 없애도 디자인이 변하지 않습니다.
그래서 단순하지만 간단하게 문제가 되는 clipPath를 아예 삭제하는 방법을 사용했습니다.
AS-IS
<svg width="12" height="11" viewBox="0 0 12 11" fill="none">
<g clip-path="url(#clip0_3200_4527)">
<path d="..." fill="#ECEEEF"/>
</g>
<defs>
<clipPath id="clip0_3200_4527">
<rect width="11" height="11" fill="white" transform="translate(0.183594)"/>
</clipPath>
</defs>
</svg>
TO-BE
<svg width="12" height="11" viewBox="0 0 12 11" fill="none">
<path
d="..."
fill="#ECEEEF"
/>
</svg>
적용 후 모든 환경에서 문제가 완전히 해결되었습니다.
| 환경 | 첫 진입 | 뒤로 가기 후 |
|---|---|---|
| iOS Safari | 정상 | 정상 |
| iOS WebView | 정상 | 정상 |
| Desktop Chrome | 정상 | 정상 |
| Desktop Safari | 정상 | 정상 |
| Android Chrome | 정상 | 정상 |