Skip to content

[FEAT] SVG Sprite 빌드 시스템 설정 - #50

Closed
ehye1 wants to merge 7 commits into
developfrom
feat/ui/47-setup-svg-sprite
Closed

[FEAT] SVG Sprite 빌드 시스템 설정#50
ehye1 wants to merge 7 commits into
developfrom
feat/ui/47-setup-svg-sprite

Conversation

@ehye1

@ehye1 ehye1 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

ISSUE 🔗

close #47



What is this PR? 🔍

SVG 아이콘을 Sprite 방식으로 번들링하는 빌드 시스템을 구축하고, <Icon name="ic_close" size={24} /> 형태로 사용할 수 있는 컴포넌트를 추가했습니다.

배경

  • 기존 구조: 아이콘을 SVG 파일 단위로 개별 import하거나 SVGR로 React 컴포넌트화하는 방식이 일반적입니다.
  • 발생 문제: SVGR 방식은 아이콘마다 별도의 React 컴포넌트를 생성해 번들 크기가 아이콘 수에 비례하여 증가하고, 아이콘을 추가할 때마다 번들에 SVG 마크업이 중복 포함됩니다. 또한 색 고정 아이콘에서 currentColor를 거치는 불필요한 레이어가 생깁니다.
  • 해결 방향: 모든 SVG를 하나의 sprite.svg로 합친 뒤 <use href="#icon-name" />으로 참조하는 Sprite 방식을 채택해 번들 크기와 런타임 DOM 노드를 최소화했습니다.

SVGR vs SVG Sprite 비교

항목 SVGR SVG Sprite (이번 PR)
아이콘 추가 시 번들 크기 아이콘마다 증가 sprite.svg 1개만 변경
런타임 DOM 노드 아이콘마다 <svg> 삽입 <use> 참조 1개
색 제어 currentColor 활용 가능 색 고정 (이번 PR 정책)
타입 안전성 파일명 기반 iconNames.ts 자동 생성 → TypeScript union type
빌드 필요 여부 없음 (webpack/vite 플러그인) pnpm icons 실행 필요

빌드 파이프라인 (packages/timo-design-system)

  • 변경 요약: svg-sprite, svgo, tsx 패키지를 추가하고 3단계 빌드 스크립트를 등록했습니다.
  • 이유: SVG 원본을 그대로 쓰면 불필요한 속성이 포함되고 파일 크기가 크기 때문에 SVGO로 최적화 후 스프라이트를 생성합니다.
  • 구현 방식: pnpm icons 한 번으로 아래 세 단계가 순서대로 실행됩니다.
pnpm icons
  └─ icons:optimize  → svgo로 src/icons/source/*.svg 최적화 (원본 덮어쓰기)
  └─ icons:sprite    → sprite.svg 생성 → apps/timo-web/public/sprite.svg
  └─ icons:names     → iconNames.ts 자동 생성 → src/icons/iconNames.ts
  • 경계 · 제약: sprite.svg는 빌드 산출물이므로 .gitignore에 추가했습니다. 아이콘을 추가하거나 수정할 때마다 pnpm icons를 재실행해야 합니다.

SVGO 설정 (svgo.config.mjs)

  • 변경 요약: removeViewBox: false, removeDimensions 옵션으로 SVGO를 설정했습니다.
  • 이유: viewBox를 제거하면 CSS로 크기 조절 시 비율이 깨지기 때문에 유지하고, 대신 width/height 고정 속성은 제거해 CSS로 크기를 자유롭게 제어할 수 있도록 했습니다.

Icon 컴포넌트 (src/icons/Icon.tsx)

  • 변경 요약: <Icon name="ic_close" size={24} /> 형태의 컴포넌트를 추가했습니다.
  • 이유: <svg><use href="#icon-name" /></svg>를 매번 직접 작성하는 불편함을 줄이고, iconNames.ts의 union type을 통해 존재하지 않는 아이콘 이름을 컴파일 타임에 잡을 수 있도록 했습니다.
  • 구현 방식: href#icon-{name} 패턴으로 sprite의 symbol을 참조합니다. size prop으로 width/height를 동시에 설정할 수 있고, rotate prop으로 90/180/270도 회전을 지원합니다. ariaHidden은 기본값 true로 스크린리더 중복 읽기를 방지합니다.
<Icon name="ic_close" size={24} />
<Icon name="ic_arrow" size={20} rotate={90} />

SvgSprite 컴포넌트 (apps/timo-web/components/SvgSprite.tsx)

  • 변경 요약: sprite.svg를 서버에서 읽어 layout.tsx에 인라인으로 주입하는 컴포넌트를 추가했습니다.
  • 이유: CDN이나 외부 파일로 sprite를 참조하면 <use href> cross-origin 이슈가 발생할 수 있습니다. 인라인 방식은 이 문제를 회피하고 첫 페인트 시점에 모든 symbol이 DOM에 존재함을 보장합니다.
  • 구현 방식: Next.js Server Component에서 fs.readFileSyncpublic/sprite.svg를 읽어 dangerouslySetInnerHTML로 주입합니다. sprite.svg가 없을 경우 (pnpm icons 미실행 상태) null을 반환해 크래시를 방지합니다.
  • 경계 · 제약: Server Component 전용입니다. "use client" 컴포넌트에서는 사용할 수 없습니다.

아이콘 추가 워크플로우

새 아이콘을 추가하거나 기존 아이콘을 수정할 때는 아래 순서를 따릅니다.

1. SVG 파일을 packages/timo-design-system/src/icons/source/ 에 추가
   (파일명 형식: ic_close.svg, ic_arrow_right.svg 등 ic_ 접두사)

2. pnpm --filter timo-design-system icons 실행

   실행 결과:
   ├── apps/timo-web/public/sprite.svg        ← 모든 아이콘이 합쳐진 스프라이트
   └── packages/timo-design-system/
       └── src/icons/iconNames.ts             ← IconName union type 자동 갱신

3. 컴포넌트에서 사용
   <Icon name="ic_close" size={24} />
   (잘못된 name 입력 시 TypeScript 에러로 즉시 확인 가능)



To Reviewers

SvgSprite가 Server Component로 동작하면서 fs를 직접 읽는 구조인데, Next.js App Router 환경에서 경계 설정이 올바른지 확인 부탁드립니다.

sprite.svg는 gitignore 대상이라 로컬에서 pnpm icons를 실행하지 않으면 아이콘이 렌더링되지 않습니다 (null 반환으로 크래시는 방지됩니다).

iconNames.ts는 자동 생성 파일이므로 직접 수정하지 않습니다.



Screenshot 📷

예시 아이콘 넣어봤을 때
image



Test Checklist ✔

  • lint-staged 통과 (커밋 훅)
  • sprite.svg 미존재 시 SvgSprite null 반환 확인
  • pnpm icons 실행 후 sprite.svg 생성 확인
  • <Icon name="ic_play" /> 렌더링 확인
  • pnpm check-types — 미실행

ehye1 added 4 commits June 29, 2026 15:37
svg-sprite, svgo, tsx 패키지를 설치하고 icons 빌드 스크립트를 등록했습니다.
svgo.config.mjs, 스프라이트 생성 스크립트, iconNames 자동 생성 스크립트,
Icon 컴포넌트를 작성하고 icons/index.ts에서 export했습니다.
SvgSprite 컴포넌트를 작성하고 layout.tsx에 인라인으로 주입했습니다.
sprite.svg를 .gitignore에 추가했습니다.
sprite.svg 파일이 없을 때 readFileSync가 ENOENT를 던지는 문제를 수정했습니다.
existsSync로 파일 존재 여부를 먼저 확인하고 없으면 null을 반환합니다.
@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ⌚ Timo-Design-system Timo 디자인 시스템 labels Jun 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 515e5c79-ed11-4893-bb20-bd03c02e7d8b

📥 Commits

Reviewing files that changed from the base of the PR and between 6cec4fe and ff64da8.

📒 Files selected for processing (1)
  • packages/timo-design-system/src/icons/generate-icon-names.ts

워크스루

SVG Sprite 빌드 체계를 추가했습니다. 아이콘 소스에서 스프라이트와 이름 타입을 생성하고, Icon 컴포넌트와 SvgSprite를 웹 앱 레이아웃에 연결합니다.

변경 사항

SVG Sprite 빌드 시스템

Layer / File(s) Summary
빌드 도구 설정
packages/timo-design-system/svgo.config.mjs, packages/timo-design-system/package.json
SVGO 설정과 아이콘 생성용 스크립트, 관련 devDependencies가 추가됩니다.
스프라이트와 이름 생성
packages/timo-design-system/src/icons/generate-sprite.ts, packages/timo-design-system/src/icons/generate-icon-names.ts, packages/timo-design-system/src/icons/iconNames.ts
SVG 소스에서 sprite.svg를 만들고, 파일명 기반 IconName 타입을 생성하며, 초기 타입 파일이 추가됩니다.
Icon 컴포넌트 공개 API
packages/timo-design-system/src/icons/Icon.tsx, packages/timo-design-system/src/icons/index.ts
IconPropsIcon을 추가하고, icons 진입점에서 IconIconName을 재export합니다.
Sprite 렌더링 연결
apps/timo-web/components/SvgSprite.tsx, apps/timo-web/app/layout.tsx, apps/timo-web/.gitignore
SvgSpritesprite.svg를 읽어 숨김 영역에 주입하고, RootLayout에 포함하며, 생성 파일은 Git 무시 대상으로 지정합니다.

예상 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~20분

연관 가능성이 있는 PR

  • Team-Timo/Timo-client#32: apps/timo-web/app/layout.tsxRootLayout 변경 흐름과 같은 컴포넌트에 대한 수정이 겹칩니다.

제안 레이블

🛠️ Setup

제안 리뷰어

  • jjangminii
  • kimminna
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 스프라이트 파이프라인은 맞지만 iconNames.tsnever로 남아 있어 이름 기반 Icon 사용 요구를 아직 충족하지 못합니다. 실제 SVG 아이콘 소스를 포함해 pnpm icons 결과가 IconName 유니온을 생성하도록 하고, 테스트용 아이콘도 함께 추가하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 SVG Sprite 빌드 시스템 설정이라는 핵심 변경을 간결하게 잘 요약합니다.
Description check ✅ Passed PR 설명은 스프라이트 빌드 시스템과 Icon/SvgSprite 추가를 직접 설명해 변경 내용과 일치합니다.
Out of Scope Changes check ✅ Passed 변경은 SVG 스프라이트 생성, 아이콘 컴포넌트, 레이아웃 연결 등 #47 범위 안에만 있습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ui/47-setup-svg-sprite

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added ✨ Feature 새로운 기능(기능성) 구현 ♥️ 혜원 혜원양 labels Jun 29, 2026
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-06-29 07:12 UTC

@github-actions

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/ 0 B 🟡 205.30 kB
/focus 0 B 🟡 205.30 kB
/home 0 B 🟡 205.30 kB
/login 0 B 🟡 205.30 kB
/onboarding 0 B 🟡 205.30 kB
/settings 0 B 🟡 205.30 kB
/settings/account 0 B 🟡 205.30 kB
/settings/policy 0 B 🟡 205.30 kB
/statistics 0 B 🟡 205.30 kB
/today 0 B 🟡 205.30 kB

공유 번들: 205.30 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: f5ff337

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/timo-web/.gitignore`:
- Around line 38-39: The .gitignore entry for the sprite asset is using a
repo-relative path instead of being relative to apps/timo-web, so it will not
ignore the intended file. Update the ignore pattern in apps/timo-web/.gitignore
to target public/sprite.svg (or /public/sprite.svg) and keep the comment/entry
aligned with the actual artifact being ignored.

In `@apps/timo-web/components/SvgSprite.tsx`:
- Around line 4-9: SvgSprite currently does synchronous filesystem checks and
reads on every render, which blocks the server render path. Move the sprite
loading logic in SvgSprite out of the render function by caching it at module
scope or using a one-time lazy initialization so existsSync and readFileSync are
not called repeatedly. Keep the component’s behavior the same by having
RootLayout still render the cached sprite content when available.

In `@packages/timo-design-system/src/icons/generate-icon-names.ts`:
- Around line 7-15: The icon name generator in generate-icon-names.ts should
make output deterministic and handle an empty source directory explicitly. Sort
the SVG-derived names before building the union so readdirSync order does not
change the generated iconNames.ts across environments, and add a names.length
=== 0 branch in the generation logic so IconName is emitted as a valid
empty/never-safe type instead of producing invalid TypeScript. Keep the fix
localized to the names mapping and content template used by the generator.

In `@packages/timo-design-system/src/icons/Icon.tsx`:
- Around line 3-4: The Icon component’s name prop is unusable because IconName
is currently never, so <Icon name="..."/> cannot type-check. Fix the upstream
icon generation in iconNames.ts so it produces a real literal union (or fails
the build when no icons are present), and ensure Icon.tsx consumes that
generated type for the name prop. Also add a generation/CI validation step so
the TypeScript artifact cannot ship with IconName = never.

In `@packages/timo-design-system/src/icons/iconNames.ts`:
- Around line 1-2: `IconName` is currently `never`, so `Icon.tsx` cannot accept
any valid icon name and `<use href="`#icon-`${name}" />` becomes unusable.
Regenerate the `IconName` type in `iconNames.ts` from the actual
`src/icons/source` icon set so it exports the real union of icon names, and
verify `Icon` continues to use that type for its `name` prop without narrowing
it to `never`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 68360a26-ad57-4d54-a735-d30805c5ba41

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4a9ec and 26a8ca6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/timo-web/.gitignore
  • apps/timo-web/app/layout.tsx
  • apps/timo-web/components/.gitkeep
  • apps/timo-web/components/SvgSprite.tsx
  • packages/timo-design-system/package.json
  • packages/timo-design-system/src/icons/.gitkeep
  • packages/timo-design-system/src/icons/Icon.tsx
  • packages/timo-design-system/src/icons/generate-icon-names.ts
  • packages/timo-design-system/src/icons/generate-sprite.ts
  • packages/timo-design-system/src/icons/iconNames.ts
  • packages/timo-design-system/src/icons/index.ts
  • packages/timo-design-system/svgo.config.mjs

Comment thread apps/timo-web/.gitignore Outdated
Comment thread apps/timo-web/components/SvgSprite.tsx Outdated
Comment thread packages/timo-design-system/src/icons/generate-icon-names.ts
Comment thread packages/timo-design-system/src/icons/Icon.tsx
Comment thread packages/timo-design-system/src/icons/iconNames.ts
ehye1 added 3 commits June 29, 2026 16:05
.gitignore 내 sprite.svg 경로가 절대 경로로 작성되어 무시되지 않던 문제를 수정했습니다.
매 렌더링마다 fs.readFileSync를 호출하던 방식을 모듈 레벨 변수로 캐싱해
첫 호출 이후 파일 I/O가 발생하지 않도록 했습니다.
아이콘 추가 순서에 따라 iconNames.ts의 union type 순서가 달라지던 문제를 수정했습니다.
.sort()를 추가해 항상 알파벳 순으로 생성되도록 했습니다.
@ehye1 ehye1 closed this Jun 30, 2026
@kimminna
kimminna deleted the feat/ui/47-setup-svg-sprite branch July 1, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⌚ Timo-Design-system Timo 디자인 시스템 ⏰ Timo-web Timo 웹 서비스 ♥️ 혜원 혜원양

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] SVG Sprite 빌드 시스템 설정

1 participant