Next.js の app ディレクトリに favicon.ico を配置するだけであれば、特に問題はありません。しかし、マーケティングページとダッシュボードで異なるアイコンを使い分けたり、ページ全体を再読み込みせずにユーザーのテーマ設定に応じてアイコンを切り替えたりする必要が生じた場合はどうでしょうか。ファイルベースの規約は手軽ですが、すべてのシナリオに対応できるわけではありません。

Next.js では、メタデータオブジェクト内の icons フィールドを利用することで、アイコンをプログラム的に宣言できます。この方法により、layout.tsx や特定の page.tsx から rel、sizes、type、さらには media 属性まで細かく制御できます。ここでは、静的なファイルからメタデータ駆動の構成へ移行するための実践的な手順を解説します。

前提条件

手順 1:再利用可能なアイコン設定を定義する

レイアウト間で同じアイコン定義を再利用できるよう、小さなヘルパーを作成しましょう。これにより、メタデータのエクスポートが整理され、将来的な更新も容易になります。

// lib/icons.ts
import { Metadata } from 'next';

export const iconConfig = {
  icon: [
    { url: '/icons/icon-16x16.png', sizes: '16x16', type: 'image/png' },
    { url: '/icons/icon-32x32.png', sizes: '32x32', type: 'image/png' },
    { url: '/icons/icon.svg', sizes: 'any', type: 'image/svg+xml' },
  ],
  apple: [
    { url: '/icons/apple-touch-icon.png', sizes: '180x180', type: 'image/png' },
  ],
  shortcut: '/icons/favicon.ico',
  other: [
    {
      rel: 'mask-icon',
      url: '/icons/safari-pinned-tab.svg',
      color: '#0066cc',
    },
  ],
} satisfies Metadata['icons'];

手順 2:ルートレイアウトで設定を適用する

ヘルパーをインポートし、メタデータエクスポートの icons フィールドにスプレッドします。これにより、app/ 直下に配置する favicon.ico、icon.png、apple-icon.png が不要になります。

// app/layout.tsx
import type { Metadata } from 'next';
import { iconConfig } from '@/lib/icons';

export const metadata: Metadata = {
  title: 'My App',
  icons: iconConfig,
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang='en'>
      <body>{children}</body>
    </html>
  );
}

手順 3:特定ルートのアイコンを上書きする

メタデータはルートツリーに沿ってマージされるため、ページ側でルートのアイコンセットを拡張または置き換えられます。ブログのサブディレクトリやテナント別ダッシュボードなど、セクションごとにブランディングを変えたい場合に便利です。

// app/dashboard/layout.tsx
import type { Metadata } from 'next';
import { iconConfig } from '@/lib/icons';

export const metadata: Metadata = {
  icons: {
    ...iconConfig,
    icon: [
      { url: '/icons/dashboard-32x32.png', sizes: '32x32', type: 'image/png' },
      { url: '/icons/dashboard.svg', sizes: 'any', type: 'image/svg+xml' },
    ],
  },
};

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <section>{children}</section>;
}

手順 4:データからアイコンを動的に生成する

feature flag、ユーザーロール、A/B テストのバリアントに応じてアイコンを切り替えたい場合は、サーバーコンポーネントでデータを取得し、その場で icons オブジェクトを構築します。次の例では、theme Cookie がダークモードを示す場合にハイコントラスト版のアイコンを選択しています。

// app/settings/page.tsx
import { cookies } from 'next/headers';
import type { Metadata } from 'next';

export async function generateMetadata(): Promise<Metadata> {
  const cookieStore = await cookies();
  const theme = cookieStore.get('theme')?.value ?? 'light';
  const suffix = theme === 'dark' ? '-dark' : '';

  return {
    icons: {
      icon: [
        { url: `/icons/settings${suffix}-32x32.png`, sizes: '32x32' },
        { url: `/icons/settings${suffix}.svg`, sizes: 'any' },
      ],
      apple: [{ url: `/icons/settings${suffix}-apple.png`, sizes: '180x180' }],
    },
  };
}

export default function SettingsPage() {
  return &lt;main&gt;Settings&lt;/main&gt;;
}

手順 5:メディアクエリによるダークモード SVG の追加

CSS で適応する単一の SVG を利用したい場合は、一度宣言し、media 属性に任せましょう。これによりアセットの重複を避け、&lt;head&gt; を軽量に保てます。

// lib/icons.ts(追加分)
export const adaptiveIconConfig = {
  icon: [
    {
      url: '/icons/adaptive.svg',
      sizes: 'any',
      type: 'image/svg+xml',
      media: '(prefers-color-scheme: light)',
    },
    {
      url: '/icons/adaptive-dark.svg',
      sizes: 'any',
      type: 'image/svg+xml',
      media: '(prefers-color-scheme: dark)',
    },
  ],
} satisfies Metadata['icons'];

よくある落とし穴

確認チェックリスト

  1. DevTools を開き、Network パネルで「img」フィルタを適用して、宣言した各アイコンが 200 を返すことを確認してください。
  2. &lt;head&gt; 内に &lt;link rel='icon'&gt;、&lt;link rel='apple-touch-icon'&gt;、&lt;link rel='mask-icon'&gt; の各タグが存在するか確認してください。
  3. OS の設定でダークモードの切り替えをテストし、再読み込みなしに正しい SVG が読み込まれることを確認してください。
  4. Mzu favicondl のオンラインチェッカー を使って簡易監査を行い、不足しているサイズや MIME タイプの不一致を検出してください。

ファイルベースの規約を維持すべきケース

プロジェクトで静的な favicon セットを 1 つだけ使用する場合は、ファイルベースのアプローチ(app/favicon.ico、app/icon.png など)が依然として最もシンプルです。TypeScript もランタイムも不要で、すぐに動作します。ルートごとの上書き、動的な選択、media や color のような高度な属性が必要な場合に限り、メタデータ API を利用しましょう。

ファイルベースのワークフローについて詳しくは、Next.js App Router の favicon を数分で追加するガイドを参照してください。PWA を構築している場合は、Web App Manifest の favicon ガイドで、これらのメタデータ宣言と併せて必要なマニフェストアイコンについて説明しています。

公式ドキュメントの要件を確認する

Next.js では、favicon やアプリアイコンについてメタデータのファイル規約に記載されています。