favicon.svg를 Vite의 public 폴더에 넣고 저장했는데, 브라우저 에는 여전히 하 사각형만 표시됩니다. 빌드는 정상이고 파일도 그 자리에 있는데, 브라우저는 파일을 찾지 못합니다. 이는 개발자가 Vite의 public 디렉터리를 다른 정적 폴더처럼 다루면서, Vite가 이 파일들을 제공하는 방식에 대한 핵심 규칙을 놓칠 때 발생합니다.

public 폴더는 안정적이고 해시 처리되지 않은 URL이 필요한 자산을 위한 Vite의 우회 경로입니다. Favicon은 가장 전형적인 사용 사례이며, Vite 입문자들이 가장 자주 혼란을 겪는 지점이기도 합니다.

왜 favicon은 Vite의 public 폴더에 있어야 할까요

Vite는 src/에서 import한 자산에 캐시 무효화를 위해 해시를 부여합니다(예: assets/logo-a3f9c2.svg). JS와 CSS에는 적합하지만 favicon에는 좋지 않습니다. 브라우저는 favicon을 적극적으로 캐싱하며, 검색 엔진은 검색 결과에 아이콘을 표시하기 위해 예측 가능한 URL을 필요로 합니다.

public/ 폴더의 파일은 번들러를 완전히 우회합니다. Vite 공식 문서에 따르면, 이 디렉터리의 모든 파일은 빌드 출력 루트로 복사되어 루트 경로에서 제공됩니다. 따라서 public/favicon.svg는 /favicon.svg가 됩니다. /public/favicon.svg가 아닙니다. 파일 이름 앞에 슬래시만 붙인 형태입니다.

사전 준비

1단계: favicon 파일을 public/에 배치

아이콘 파일을 프로젝트 루트의 public/ 디렉터리에 직접 배치합니다. 대부분의 Vite 스캐폴드에서는 이 폴더가 자동으로 생성됩니다.

my-vite-app/
├── public/
│   ├── favicon.svg
│   ├── favicon.ico
│   └── apple-touch-icon.png
├── src/
├── index.html
└── package.json

간단하고 관례적인 파일 이름을 사용합니다. 브라우저는 추가 설정 없이 루트 경로에서 favicon.svg, favicon.ico, apple-touch-icon.png를 찾습니다.

2단계: 루트 상대 경로로 파일 참조

프로젝트 루트의 index.html을 엽니다(public/index.html이 아닙니다 — Vite에는 해당 파일이 존재하지 않습니다). <head> 안에 /로 시작하는 절대 경로의 link 태그를 추가합니다. public 폴더는 의도적으로 import resolver를 우회하므로, Vite 프로젝트에서 자산 경로를 하드코딩하는 것은 이 경우가 유일합니다.

<head>
  <link rel='icon' type='image/svg+xml' href='/favicon.svg'>
  <link rel='icon' type='image/x-icon' href='/favicon.ico'>
  <link rel='apple-touch-icon' href='/apple-touch-icon.png'>
</head>

HTML 명세는 rel='icon'을 favicon을 선언하는 표준 방식으로 정의합니다(WHATWG HTML Living Standard). 브라우저는 type과 sizes 속성을 기준으로 가장 일치하는 항목을 선택합니다.

3단계: 다크 모드 변형 추가

최신 브라우저는 다크 테마에서 자동으로 활성화되는 두 번째 SVG를 지원합니다. media query를 사용해 link 태그를 하나 더 추가하면, 사용자의 시스템이 다크 모드로 전환될 때 적절한 아이콘이 표시됩니다.

<link rel='icon' type='image/svg+xml' href='/favicon.svg' media='(prefers-color-scheme: light)'>
<link rel='icon' type='image/svg+xml' href='/favicon-dark.svg' media='(prefers-color-scheme: dark)'>

다크 모드 SVG favicon에 대한 자세한 내용은 favicon dark mode 가이드를 참고하시기 바랍니다.

4단계: 빌드 출력 확인

npm run build를 실행하고 dist/를 확인합니다. favicon 파일은 번들러의 영향을 받지 않은 채 루트에 나타나야 합니다.

dist/
├── assets/
├── favicon.svg
├── favicon.ico
└── index.html

npm run preview를 실행한 뒤 DevTools의 Network 탭을 열어도 됩니다. favicon.svg, favicon.ico, apple-touch-icon.png 요청이 404가 아닌 200을 반환하는 것을 확인할 수 있습니다.

한 실수

대부분의 Vite favicon 버그는 다음 실수 중 하나에서 비롯됩니다. 이 비어 있거나 일반 아이콘만 표시된다면, 먼저 이 목록을 확인해 보시기 바랍니다.

public 폴더만으로 부족한 경우

public 폴더는 정적이고 변하지 않는 favicon을 위한 적합한 위치입니다. 브라우저가 안정적으로 캐싱하고 검색 엔진이 색인할 수 있는 안정적인 URL을 제공합니다. 이후 알림 배지, 사용자 아바타, 실시간 상태 표시 같은 동적 favicon이 필요해지면, 런타임에 href 속성을 교체할 JavaScript가 필요합니다. dynamic favicon JavaScript 가이드에서 해당 패턴을 자세히 다룹니다.

먼저 적절한 아이콘 파일을 만드는 데 도움이 필요하신가요? Mzu favicondl은 단일 소스 이미지에서 SVG, PNG, ICO, Apple Touch 변형을 생성합니다. 생성된 파일을 public/에 넣고 link 태그를 추가하면, 몇 분 안에 Vite 앱에 전문적인 탭 아이콘이 갖춰집니다.