Nuxtアプリを公開したのに、ブラウザのタブにデフォルトのファビコンが表示されたまま…。さらに、マーケティングチームからはチェックアウトページと管理ダッシュボードで異なるアイコンを使いたいと言われている。ハードコードしたlinkタグでは限界がある。Nuxtにはapp.head設定とuseHeadコンポーザブルによるプログラム的な制御手段があり、この2つを理解することが、フレームワークと戦うか、うまく活用するかの分かれ目になる。
このガイドでは、Nuxtのヘッド設定システムを使ったファビコン管理に焦点を当てる。ファイルベースのメタデータ手法については、Nuxtファビコンガイドで解説済みだ。ページごとのアイコン、ダークモード切り替え、ランタイムでの差し替えが必要なら、ヘッドAPIが最適だ。
前提条件
- Nuxt 3.xまたは4.xプロジェクト
- 生成済みのファビコンファイル(PNG、SVG、ICO)— 必要なサイズが不明な場合は、ファビコンサイズガイドを参照
- Vue 3の
<script setup>ブロックに関する基本的な知識
ステップ1: app.headでデフォルトのファビコンを設定する
nuxt.config.tsのapp.headプロパティは、フレームワーク全体のデフォルト設定だ。ここに記述した内容は、ページが上書きしない限り、すべてのページの<head>に注入される。
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }
]
}
}
})
アイコンファイルはpublic/ディレクトリに配置し、Nuxtがルートで配信できるようにする。NuxtのSEO・メタドキュメントによると、link配列は、基盤となるunheadエンジンが受け付ける形式と同じであり、これはhtml.spec.whatwg.orgのWHATWGのrel="icon"仕様にも準拠している。
ステップ2: useHeadでページごとにファビコンを上書きする
特定のルートで異なるアイコンを使いたい場合がある。useHeadコンポーザブルを使うと、そのページに限ってグローバル設定を上書きするエントリを追加できる。
<script setup lang="ts">
useHead({
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/icons/admin-favicon.svg' }
]
})
</script>
<template>
<div>Admin Dashboard</div>
</template>
useHead()は<script setup>ブロックの先頭で呼び出す。NuxtがSSRインジェクションを処理するため、上書きは初期HTMLに反映される。ハイドレーション時に間違ったアイコンが一瞬表示されることも、クライアントサイドでのパッチ処理も不要だ。
ステップ3: 状態に基づいてファビコンを動的に切り替える
通知数やテーマの変更に応じてファビコンを変更したい場合は、useHeadをリアクティブなrefやcomputedと組み合わせる。
<script setup lang="ts">
const cartCount = useCartCount()
useHead({
link: [
{
rel: 'icon',
type: 'image/svg+xml',
href: computed(() =>
cartCount.value > 0
? `/icons/cart-${cartCount.value}.svg`
: '/favicon.svg'
)
}
]
})
</script>
computedのhrefは、cartCountが変更されるたびに更新される。NuxtのuseHeadはリアクティビティを追跡し、<link>タグをその場で書き換える。ページ全体のリロードも、手動のDOM操作も不要だ。さらに進んでcanvasベースのバッジを追加したい場合は、動的ファビコンのJavaScriptガイドで基盤となるパターンを解説している。
ステップ4: ダークモード対応のファビコンをサポートする
モダンブラウザは、アイコンリンクのmedia属性を尊重する。これをuseHeadと組み合わせれば、JavaScriptを一切使わずにテーマ対応アイコンを実現できる。
useHead({
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon-light.svg', media: '(prefers-color-scheme: light)' },
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon-dark.svg', media: '(prefers-color-scheme: dark)' }
]
})
ブラウザはユーザーのシステム設定に基づいて適切なアイコンを選択する。これはファビコンのダークモードガイドで説明したパターンと同じだが、生のHTMLではなくNuxtのヘッドAPIを介して実現している。
よくある落とし穴
- 深くネストされた子コンポーネントで
useHeadを使う。 動作はするが、SSRのタイミングが予測不能になり、タグが重複するリスクがある。ページレベルの<script setup>に置くこと。 type属性を忘れる。 複数のrel="icon"リンクが同じパスやファイル名を共有していると、ブラウザが誤ったファイルを配信する可能性がある。明確にするため常にtypeを設定しよう。- ファイルベースのメタデータと
useHeadを混在させる。 Nuxtのファイルベースのアイコン(app/またはpublic/内)は、明示的なapp.headエントリと衝突し、<link>タグが重複する可能性がある。プロジェクトごとにどちらか一方のアプローチを選ぼう。 - 絶対パスをハードコードする。
/favicon.svgのようなルート相対パスを使うことで、ルートやサブドメイン、リバースプロキシの背後でもアイコンが正しく読み込まれる。
まとめ
app.head設定はデフォルトを処理し、useHeadは動的な処理を担当する。この2つを組み合わせれば、単一の静的アイコンからページごとの切り替え、リアクティブなバッジまで、Nuxtアプリで通常必要となるあらゆるファビコンシナリオをカバーできる。サードパーティのヘッドプラグインやNuxtのデフォルトと戦う必要はない。
アイコンファイルをまだ生成していない場合は、Mzu favicondlでマルチ解像度パッケージ(SVG、PNG、ICO、Apple Touch)をワンクリックで生成できる。ファイルをpublic/に置き、app.headに設定すれば、数分でタブがプロフェッショナルな見た目になる。