ブラウザのタブに表示される小さなアイコン(ファビコン)や、スマートフォンのホーム画面に追加したときのアイコンを設定するとき、以前は <head> の中に <link rel="icon"> を自分で並べる必要がありました。Next.js の App Router では、app ディレクトリに決まった名前の画像ファイルを置くだけで、必要な <link> タグが自動で出力されます。この記事では favicon.ico / icon / apple-icon という3つのファイル規約の使い方、複数サイズの指定方法、icon.tsx でコードからアイコンを生成する方法、そしてアイコンが反映されないときの原因までを解説します。App Router(Next.js 13.3 以降)が対象です。
目次
app に画像を置くだけでタグが出力される仕組み
App Router には「メタデータファイル規約」と呼ばれる仕組みがあります。robots.txt や sitemap.xml、OGP 画像の opengraph-image などと同じように、アイコンも決められた名前のファイルを置くだけで Next.js が中身を評価し、適切な <link> タグを <head> に追加してくれます。
たとえば app/favicon.ico を置けば、レイアウトに1行も書かなくてもファビコンが有効になります。本番ビルドではファイル名にハッシュが付いた URL で配信されるため、キャッシュの扱いも Next.js 側が面倒を見てくれます。
app/ ├── favicon.ico ← ブラウザタブ用のファビコン ├── icon.png ← 汎用のアイコン ├── apple-icon.png ← iOS のホーム画面用アイコン ├── layout.tsx └── page.tsx
使えるファイル名・拡張子と置ける場所
画像ファイルで設定する場合、使えるファイル名は次の3種類です。拡張子と、置ける場所がそれぞれ決まっています。
| ファイル名 | 使える拡張子 | 置ける場所 | 用途 |
|---|---|---|---|
favicon | .ico | app/ の直下のみ | ブラウザのタブやブックマークに出るファビコン |
icon | .ico / .jpg / .jpeg / .png / .svg | app/ 配下の任意のセグメント | 汎用のアプリアイコン(rel="icon") |
apple-icon | .jpg / .jpeg / .png | app/ 配下の任意のセグメント | iOS のホーム画面用アイコン(rel="apple-touch-icon") |
注意したいのは favicon.ico だけが特別扱いで、ルートの app/ 直下にしか置けない点です。「トップページと管理画面でアイコンを変えたい」のようにセグメントごとに出し分けたい場合は、favicon ではなく icon を使います。icon と apple-icon は app/blog/icon.png のように、どのセグメントにも置けます。
また apple-icon には .svg と .ico が使えません。Apple のホーム画面用アイコンは PNG などのラスター画像を用意してください。180×180px 程度の PNG が一般的です。
実際に出力される link タグ
ファイルを置くと、それぞれ次のようなタグが <head> に追加されます。まず app/favicon.ico の場合です。
<link rel="icon" href="/favicon.ico" sizes="any" />
icon と apple-icon の場合は、rel に加えて type と sizes も自動で埋まります。
<link rel="icon" href="/icon?<generated>" type="image/<generated>" sizes="<generated>" /> <link rel="apple-touch-icon" href="/apple-icon?<generated>" type="image/<generated>" sizes="<generated>" />
<generated> の部分は、Next.js が実際のファイルを読み取って決めます。たとえば 32×32px の PNG を置いた場合は type="image/png"、sizes="32x32" になります。sizes を自分で書く必要はありません。
例外として、拡張子が .svg の場合や、画像のサイズを判定できなかった場合は sizes="any" が付きます。SVG は解像度に依存しないので、これで正しい挙動です。
複数サイズのアイコンを用意する
アイコンは1つだけでなく、複数のサイズを同時に登録できます。方法は簡単で、ファイル名の末尾に数字を付けて連番にするだけです。icon.png、icon1.png、icon2.png のように置けば、それぞれに対応した <link> タグがすべて出力されます。
app/ ├── favicon.ico ├── icon.svg ← ベクター(sizes="any" が付く) ├── icon1.png ← 32x32 ├── icon2.png ← 192x192 └── apple-icon.png ← 180x180
連番のファイルは辞書順(lexical order)で並べ替えられて出力されます。数値順ではないため、10個以上用意するときは icon10.png が icon2.png より前に来る点に注意してください。順番を厳密に管理したい場合は icon01.png のようにゼロ埋めしておくと安全です。
apple-icon1.png、apple-icon2.png のように、apple-icon でも同じ連番の仕組みが使えます。
icon.tsx でコードからアイコンを生成する
画像ファイルを用意する代わりに、icon.tsx(.js / .ts でも可)を置いてコードでアイコンを作ることもできます。next/og の ImageResponse を使うと、JSX と CSS で書いた見た目をそのまま画像に変換できます。ロゴ画像を用意しなくても、頭文字1文字のシンプルなアイコンならこれで十分です。
import { ImageResponse } from 'next/og';
// 画像のメタデータ
export const size = {
width: 32,
height: 32,
};
export const contentType = 'image/png';
// 画像の生成
export default function Icon() {
return new ImageResponse(
(
<div
style={{
fontSize: 24,
background: 'black',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
}}
>
A
</div>
),
{
// 上で定義した size をそのまま画像サイズに使う
...size,
}
);
}
デフォルトエクスポートした関数が画像を返す、という形です。返せるのは Blob や ArrayBuffer、Response などで、ImageResponse はこの条件を満たしています。ImageResponse は flexbox と一部の CSS プロパティしかサポートしていないので、display: grid などは使えません。display: flex の指定を忘れると描画されないことがあるため、コンテナには必ず書いておきましょう。
このファイルから出力されるタグは次のようになります。ファイルベースのアイコンと同じく、URL は Next.js が管理します。
<link rel="icon" href="/icon?<generated>" type="image/png" sizes="32x32" />
size と contentType のエクスポート
コードで生成する場合、画像そのものからはサイズや形式を推測できません。そこで size と contentType を名前付きエクスポートして、出力されるタグの属性を指定します。
| エクスポート | 型 | 反映される属性 |
|---|---|---|
size | { width: number; height: number } | sizes="32x32" |
contentType | string(画像の MIME タイプ) | type="image/png" |
どちらも省略可能ですが、指定しておくとブラウザが適切なサイズのアイコンを選びやすくなります。先ほどの例のように ...size で ImageResponse のオプションに展開すれば、宣言したサイズと実際の画像サイズがずれる心配もありません。
apple-icon.tsx も同じ書き方
iOS のホーム画面用アイコンをコードで作る場合は、ファイル名を apple-icon.tsx にするだけです。中身の書き方は icon.tsx とまったく同じで、rel="apple-touch-icon" のタグが出力されます。ホーム画面のアイコンは大きめに表示されるので、size は 180×180 程度にしておくとよいでしょう。
import { ImageResponse } from 'next/og';
export const size = {
width: 180,
height: 180,
};
export const contentType = 'image/png';
export default function AppleIcon() {
return new ImageResponse(
(
<div
style={{
fontSize: 120,
background: '#0f172a',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#ffffff',
}}
>
W
</div>
),
{ ...size }
);
}
なお、コードで生成できるのは icon と apple-icon だけで、favicon はコードから生成できません。favicon.tsx を置いても機能しないので、favicon.ico という実ファイルを用意するか、icon で代用してください。
また、生成されるアイコンは既定でビルド時に作られてキャッシュされます。リクエストごとに内容を変える必要がなければ、この静的な最適化がそのまま効くので、パフォーマンスを気にせず使えます。
アイコンが変わらない・反映されないときの確認ポイント
ファビコンは「差し替えたのに古いまま」という相談がとても多い部分です。原因はいくつかのパターンに分かれるので、上から順に確認していくと切り分けられます。
ブラウザのキャッシュが最も多い原因
ファビコンはブラウザが特に強くキャッシュするリソースで、ページを再読み込みしても古い画像が表示され続けることがよくあります。まずはシークレットウィンドウで開いてみてください。そこで新しいアイコンが出るなら、コードは正しく、単にキャッシュが残っているだけです。通常のウィンドウではスーパーリロード(Shift を押しながら再読み込み)や、閲覧履歴からのキャッシュ削除で解消します。
それでも変わらない場合は、アイコンの URL(/favicon.ico や /icon)をアドレスバーに直接入力して開いてみます。そこに新しい画像が表示されていれば、配信自体は成功しています。
public フォルダーの favicon.ico が残っている
Pages Router や他のフレームワークから移行した場合、public/favicon.ico が残っていることがあります。public 内のファイルはルート URL(/favicon.ico)でそのまま配信されるため、app/favicon.ico と同じパスを取り合う形になります。公式ドキュメントも、favicon.ico のような静的なメタデータファイルは public ではなく app 内の特別なファイルとして置くことを推奨しています。どちらか一方に統一し、App Router で書くなら public 側は削除してください。
あわせて、public 配下のファイルには Cache-Control: public, max-age=0 という控えめなキャッシュヘッダーしか付きません。一方 app のメタデータファイルは本番ビルドでハッシュ付きの URL になるため、更新の反映という点でも app に置いたほうが扱いやすくなります。
ファイル名・拡張子・置き場所が規約から外れている
この仕組みは名前が完全に一致していないと動きません。favicon.png(.ico 以外)、apple-touch-icon.png(正しくは apple-icon)、app/icons/icon.png(セグメント直下ではない)といった間違いは、エラーにならず「ただタグが出ない」だけなので気づきにくいところです。app/(marketing)/favicon.ico のようにルートグループの中へ入れてしまうケースもありますが、favicon はあくまで app/ の直下に置きます。
確認するときは、ブラウザの開発者ツールで <head> の中身を見て <link rel="icon"> が出力されているかを調べます。タグ自体が無ければファイル名か置き場所の問題、タグはあるのに古い画像ならキャッシュの問題、と切り分けられます。
ビルド結果が古いまま残っている
アイコンは既定でビルド時に生成・キャッシュされるため、開発サーバーを起動したままファイルを差し替えると古い結果が残ることがあります。開発サーバーを一度止めて起動し直し、それでも直らなければ .next ディレクトリを削除してから next build をやり直してください。本番の見た目を確認するときは next build と next start で確かめるのが確実です。
metadata の icons と二重に指定している
アイコンは layout.tsx の metadata オブジェクトにある icons フィールドでも指定できます。ファイル規約と併用すると <link> タグが重複して出力され、どちらが使われるか分かりにくくなることがあります。ファイルを置く方式に寄せるなら icons の指定は消し、逆に外部 CDN の画像を使いたいなど明示的に URL を指定したい場合はファイル側を置かない、というように片方に統一しておくと混乱しません。
まとめ
App Router では、app ディレクトリに favicon.ico / icon.(ico・jpg・jpeg・png・svg) / apple-icon.(jpg・jpeg・png) を置くだけで、rel・type・sizes まで含めた <link> タグが自動で出力されます。favicon は app/ 直下のみ、icon と apple-icon は任意のセグメントに置ける、という違いを押さえておきましょう。複数サイズを用意したいときは icon1.png、icon2.png のように連番にすれば、すべてのタグが出力されます(並び順は辞書順)。
コードで作りたい場合は icon.tsx や apple-icon.tsx で ImageResponse を返し、size と contentType をエクスポートして属性を指定します。favicon だけはコードから生成できないので、実ファイルか icon で代用してください。差し替えたのに反映されないときは、ブラウザキャッシュ、public/favicon.ico の残骸、ファイル名の綴り、ビルドし直しの4点を順に確認すれば、たいていは原因にたどり着けます。