Nuxtアプリを公開したのに、ブラウザのタブにデフォルトのファビコンが表示されたまま…。さらに、マーケティングチームからはチェックアウトページと管理ダッシュボードで異なるアイコンを使いたいと言われている。ハードコードしたlinkタグでは限界がある。Nuxtにはapp.head設定とuseHeadコンポーザブルによるプログラム的な制御手段があり、この2つを理解することが、フレームワークと戦うか、うまく活用するかの分かれ目になる。

このガイドでは、Nuxtのヘッド設定システムを使ったファビコン管理に焦点を当てる。ファイルベースのメタデータ手法については、Nuxtファビコンガイドで解説済みだ。ページごとのアイコン、ダークモード切り替え、ランタイムでの差し替えが必要なら、ヘッドAPIが最適だ。

前提条件

ステップ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を介して実現している。

よくある落とし穴

まとめ

app.head設定はデフォルトを処理し、useHeadは動的な処理を担当する。この2つを組み合わせれば、単一の静的アイコンからページごとの切り替え、リアクティブなバッジまで、Nuxtアプリで通常必要となるあらゆるファビコンシナリオをカバーできる。サードパーティのヘッドプラグインやNuxtのデフォルトと戦う必要はない。

アイコンファイルをまだ生成していない場合は、Mzu favicondlでマルチ解像度パッケージ(SVG、PNG、ICO、Apple Touch)をワンクリックで生成できる。ファイルをpublic/に置き、app.headに設定すれば、数分でタブがプロフェッショナルな見た目になる。