Vue 3 のプロジェクトを Vite で新規作成して、開発 server を立ち上げ、ブラウザのタブを見てみます。ブランドアイコンを置くべき場所に、デフォルトの Vite ロゴが陣取っているはずです。修正はプロジェクトの public/ フォルダで行います。Vite 特有の落とし穴さえ押さえれば、設定は 3 分程度で完了します。
前提条件
必要になるのは、Vite で動作している Vue 3 プロジェクトと、追加したい favicon ファイル一式だけです。まだ用意していない場合は、Mzu favicondl を使って ICO、PNG、SVG を含む完全なバンドルを生成し、そのまま使える状態でダウンロードしてください。Webpack 特有の挙動や copy-webpack-plugin のルールは一切不要です。Vite は public フォルダを static な asset として扱い、設定なしで処理します。
ステップ 1:public/ フォルダを開く
Vue + Vite のスキャフォールドには、プロジェクトルートに必ず public/ ディレクトリが用意されています。Vite の public ディレクトリに関するドキュメントによれば、ここに置いたファイルは変更されずにビルド出力へコピーされ、ルートパスから配信されます。
つまり public/favicon.ico は /favicon.ico としてアクセスでき、/public/favicon.ico ではありません。この違いを見落とすことが、Vue アプリで本番環境のタブアイコンが壊れる最もよくある原因です。
ステップ 2:favicon ファイルを配置する
favicon 関連ファイル一式を public/ 直下に置いてください。
public/
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── site.webmanifest
└── favicon.svg
Vite はフォルダごと dist/ 出力へそのままコピーします。import や変換は一切かかりません。これらのファイルを Vue コンポーネントから import しないでください。これらはバンドルの外側に置くことを前提としています。現在意味のあるサイズについては、Favicon Sizes Guide で詳しく解説しています。
ステップ 3:index.html に link タグを追加する
Vite が Vue アプリをマウントする index.html をプロジェクトルートで開きます。<head> の中に link rel="icon" の宣言を追加してください。
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/favicon-32x32.png" type="image/png" sizes="32x32" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</head>
記述の順序は重要です。ブラウザはサポートしている最初の rel="icon" エントリを採用するため、まず SVG を置き、続いて PNG のフォールバック、最後に ICO という構成にします。すべてのパスは /public/ ではなく / から始めてください。
ステップ 4:ダークモード対応の SVG を追加する
ダークモード時にアイコンを切り替えたい場合は、Vue のリアクティビティを使うのではなく、SVG 自体にメディアクエリを記述してください。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
circle { fill: #1a1a1a; }
@media (prefers-color-scheme: dark) {
circle { fill: #fafafa; }
}
</style>
<circle cx="16" cy="16" r="14" />
</svg>
ブラウザが自動的に fill を切り替えるため、タブ表示で watch() を呼んだりライフサイクルフックを使う必要はありません。より詳しいパターンについては、Favicon Dark Mode Guide を参照してください。
ステップ 5:開発環境と本番環境で検証する
npm run dev を実行し、以下の 3 点を確認します。
- DevTools → Network で "favicon" でフィルタし、すべてのファイルが
200 OKを返すこと。 - ブラウザのタブにデフォルトの Vite ロゴではなく、自分のアイコンが表示されていること。
curl http://localhost:5173/favicon.svgが SVG のバイト列を返すこと。
続いて npm run build を実行し、dist/ を確認します。public/ に置いたすべてのファイルが、変更されず dist/ のルートにそのまま出力されているはずです。本番環境で何かが欠けている場合は、ホスティングのリライトルールが原因の可能性が高いので、vercel.json や _redirects を確認してください。
よくある落とし穴
- パスの記述ミス:
/favicon.svgと書くべきところを/public/favicon.svgと書いてしまうケース。Vite は配信 URL から/public/を取り除くため、ファイルが解決されません。 - 配置フォルダの誤り:
public/ではなくsrc/assets/にファイルを置いてしまうケース。バンドルされる asset はimport文が必要で、/favicon.svgでは描画されません。 - ブラウザキャッシュの古さ:ブラウザは ICO を積極的にキャッシュします。Cmd/Ctrl + Shift + R でハードリロードするか、アイコン URL に
?v=2を付与してください。 - Manifest の不整合:
site.webmanifestが参照しているアイコンパスが、public/内の実際のファイル名と一致していないケース。両ファイルを一行ずつ突き合わせて整合性を取る必要があります。
まとめ
Vue + Vite の public フォルダを使ったワークフローは、モダンな Web 開発の中でも特にクリーンな favicon 設定の一つです。ファイルを配置し、index.html に link タグを記述すれば、Vite がすべてをそのまま dist/ にコピーします。プラグインの設定も、head への注入の挙動も、Vue Router によるリクエストの割り込みも一切なく、ルートパスから配信される static な asset として扱われます。
Mzu favicondl で favicon パッケージを生成し、index.html に link タグを貼り付ければ、あらゆるブラウザタブで Vue アプリがきちんと見えるようになります。