Next.js・React・Vue.jsでファビコンを設定する方法|フレームワーク別に解説
SPAフレームワークのファビコン設定は、従来のHTMLとは勝手が違う
静的なHTMLサイトであれば、<head>に<link rel="icon">を1行追加するだけでファビコンは表示されます。ところがNext.js・React・Vue.jsといったSPAフレームワークでは、ファイルの配置場所や設定方法がフレームワークごとに異なります。
「public/に置けばいいんでしょ?」——半分正解ですが、それだけでは済まないケースも多いです。Next.js App Routerではapp/ディレクトリに置くだけで自動認識される仕組みがありますし、Nuxtではnuxt.config.tsでの宣言が基本です。さらに、JavaScriptで動的にファビコンを切り替えたり、PWAのマニフェストと連携させたりといった、フレームワークならではのテクニックもあります。
この記事では、Next.js 15・React 19(Vite / CRA)・Vue 3 / Nuxt 3を対象に、各フレームワークでのファビコン設定方法をコピペ可能なコード付きで解説します。
ファビコンの画像ファイルをまだ準備していない方は、先にファビコンの作り方を参照してください。ICOやPNGなどファイル形式の選び方については以下の記事でまとめています。
Next.js(App Router)でのファビコン設定
Next.js 13以降のApp Routerでは、ファビコン設定がファイルベースで完結します。設定ファイルに何も書かなくても、所定のファイル名で配置するだけでNext.jsが自動的に<head>タグを生成してくれます。
ファイル配置だけで完了する方法
app/ディレクトリのルートに、以下のファイル名で画像を置きます。
app/
├── favicon.ico ← ブラウザタブ用(自動で <link rel="icon"> が生成)
├── icon.png ← 32x32 PNG(オプション)
├── icon.svg ← SVGファビコン(オプション)
└── apple-icon.png ← 180x180 Apple Touch Icon(オプション)
favicon.icoをapp/直下に配置すると、Next.jsがビルド時に検出して以下のようなHTMLを自動挿入します。
<link rel="icon" href="/favicon.ico" sizes="any" />
icon.pngやapple-icon.pngも同様に自動認識されます。従来のpublic/ディレクトリではなくapp/ディレクトリに置く点に注意してください。
metadata APIでの設定
レイアウトファイルでmetadataオブジェクトを使って明示的に指定することもできます。
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
icons: {
icon: [
{ url: "/favicon.ico", sizes: "32x32" },
{ url: "/icon.svg", type: "image/svg+xml" },
],
apple: [
{ url: "/apple-touch-icon.png", sizes: "180x180" },
],
},
};
この方法は、ファイルの配置場所をpublic/にしたい場合や、サイズ・タイプを細かく制御したい場合に使います。ファイルベースの自動認識とmetadataAPIの両方が存在する場合は、metadataAPIが優先されます。
icon.tsxで動的にアイコンを生成する
App Routerの強力な機能として、コードでファビコンを動的生成できるicon.tsxがあります。next/ogのImageResponseAPIを使って、JSXからPNG画像をビルド時に生成します。
// app/icon.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export const size = { width: 32, height: 32 };
export const contentType = "image/png";
export default function Icon() {
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#2563eb",
borderRadius: 6,
color: "#fff",
fontSize: 22,
fontWeight: 700,
}}
>
F
</div>
),
{ ...size }
);
}
このファイルをapp/icon.tsxとして保存するだけで、ビルド時にPNGが生成され<link rel="icon">に自動設定されます。Apple Touch Icon用にはapp/apple-icon.tsxを同様に作成できます。
この仕組みを活用すると、環境ごとにファビコンを出し分けることもできます。
// app/icon.tsx — 環境別ファビコン
import { ImageResponse } from "next/og";
export const runtime = "edge";
export const size = { width: 32, height: 32 };
export const contentType = "image/png";
export default function Icon() {
const isDev = process.env.NODE_ENV === "development";
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: isDev ? "#f59e0b" : "#2563eb",
borderRadius: 6,
color: "#fff",
fontSize: 22,
fontWeight: 700,
}}
>
{isDev ? "D" : "F"}
</div>
),
{ ...size }
);
}
開発環境では黄色い「D」、本番環境では青い「F」のファビコンが生成されます。複数のNext.jsプロジェクトを同時に開発しているとき、タブを見分けるのに便利です。
Next.js(Pages Router)でのファビコン設定
Pages Routerを使っているプロジェクトでは、App Routerの自動認識機能は使えません。従来通りpublic/ディレクトリへの配置と、HTMLへの明示的な記述が必要です。
基本の設定
public/
├── favicon.ico
├── favicon-32x32.png
├── favicon-16x16.png
├── apple-touch-icon.png
└── site.webmanifest
_document.tsxでの設定
pages/_document.tsxで<Head>コンポーネント内にlinkタグを追加します。
// pages/_document.tsx
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="ja">
<Head>
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
_document.tsxは全ページ共通のHTMLシェルを定義するファイルなので、ここに書いたlinkタグはすべてのページに適用されます。
next/headでページ単位の設定
特定のページだけファビコンを変えたい場合は、next/headを使います。
// pages/admin.tsx
import Head from "next/head";
export default function AdminPage() {
return (
<>
<Head>
<link rel="icon" href="/admin-favicon.ico" />
</Head>
<main>{/* 管理画面の内容 */}</main>
</>
);
}
ただし、ページ単位でファビコンを変更するケースは稀です。通常は_document.tsxへの記述で十分です。
React(Vite)でのファビコン設定
Vite + Reactプロジェクトでは、index.htmlとpublicディレクトリの組み合わせが基本です。
基本の設定手順
1. ファビコンファイルをpublic/に配置する
public/
├── favicon.ico
├── favicon.svg
├── favicon-32x32.png
├── apple-touch-icon.png
└── site.webmanifest
2. index.htmlの<head>にlinkタグを追加する
Viteプロジェクトのルートにあるindex.htmlを編集します。デフォルトでは/vite.svgへのリンクが入っているので、これを差し替えます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- ファビコン設定 -->
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
public/内のファイルはビルド時にそのまま出力ディレクトリ(dist/)にコピーされるため、パスは/favicon.icoのようにルート相対で指定します。
vite-plugin-faviconを使う方法
複数サイズのファビコンを自動生成したい場合は、vite-plugin-faviconプラグインが使えます。
npm install vite-plugin-favicon --save-dev
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { ViteFaviconsPlugin } from "vite-plugin-favicon";
export default defineConfig({
plugins: [
react(),
ViteFaviconsPlugin({
logo: "src/assets/logo.png",
// 元画像1枚から各サイズのファビコンを自動生成
}),
],
});
ただし、プラグインに頼らず手動で各サイズを用意する方が、出力結果を完全にコントロールできます。当サイトの一括生成ツールを使えば、1枚の画像からファビコン・Apple Touch Icon・PWAアイコンをまとめてダウンロードでき、HTMLスニペットもコピーできます。

React(Create React App)でのファビコン設定
CRA(Create React App)はReactの公式スキャフォールディングツールとして長く使われてきました。新規プロジェクトではViteが推奨されていますが、既存のCRAプロジェクトを運用しているケースは2026年時点でもまだ多いでしょう。
デフォルトの構成
CRAで作成したプロジェクトには、最初から以下のファビコン関連ファイルが含まれています。
public/
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
ファビコンの差し替え手順
1. public/favicon.icoを差し替える
既存のfavicon.icoを削除し、自分のファビコンを同じファイル名で配置します。
2. public/index.htmlのlinkタグを確認・修正する
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
CRAでは%PUBLIC_URL%がpublic/ディレクトリのルートに置換されます。
3. manifest.jsonを更新する
{
"short_name": "My App",
"name": "My Application",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
logo192.pngとlogo512.pngも自分のアイコン画像に差し替えてください。これらはPWAとしてホーム画面に追加する際に使われます。
Vue.js / Nuxt 3でのファビコン設定
Vite + Vue.js(Nuxtなし)
Vite + Vue.jsの場合、設定方法はVite + Reactとほぼ同じです。public/ディレクトリにファビコンを置き、index.htmlにlinkタグを追加します。
<!-- index.html -->
<head>
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</head>
Viteがpublic/内のファイルをそのままビルド出力にコピーする点も同様です。
Nuxt 3での設定
Nuxt 3ではnuxt.config.tsのapp.headでファビコンを宣言的に設定します。
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
link: [
{ rel: "icon", href: "/favicon.ico", sizes: "32x32" },
{ rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
{
rel: "icon",
type: "image/png",
sizes: "32x32",
href: "/favicon-32x32.png",
},
{
rel: "apple-touch-icon",
sizes: "180x180",
href: "/apple-touch-icon.png",
},
{ rel: "manifest", href: "/site.webmanifest" },
],
},
},
});
ファビコン画像はpublic/ディレクトリに配置します。
public/
├── favicon.ico
├── favicon.svg
├── favicon-32x32.png
├── apple-touch-icon.png
└── site.webmanifest
useHeadで動的に変更する
Nuxt 3のuseHeadコンポーザブルを使うと、ページ単位やコンポーネント単位でファビコンを動的に変更できます。
<script setup lang="ts">
// ダークモード連動でファビコンを切り替える例
const colorMode = useColorMode();
useHead({
link: [
{
rel: "icon",
type: "image/png",
href: computed(() =>
colorMode.value === "dark"
? "/favicon-dark.png"
: "/favicon-light.png"
),
},
],
});
</script>
useHeadはリアクティブなので、computedやrefの値が変わるとファビコンも自動的に更新されます。nuxt.config.tsのapp.headは静的な設定に、useHeadは動的な設定に使い分けるのが定石です。
JavaScriptでの動的ファビコン切替
フレームワークに限らず、JavaScriptでファビコンを動的に切り替えるテクニックがあります。チャットアプリの未読通知、テーマの切替連動、開発環境の識別などに活用できます。
基本:linkタグのhrefを差し替える
もっともシンプルな方法は、<link rel="icon">のhref属性を書き換えることです。
function setFavicon(url: string) {
const link: HTMLLinkElement =
document.querySelector('link[rel="icon"]') ||
document.createElement("link");
link.rel = "icon";
link.href = url;
document.head.appendChild(link);
}
// 使用例
setFavicon("/favicon-dark.png");
テーマ切替と連動させる
OS のテーマ設定(ライトモード/ダークモード)に応じてファビコンを切り替えるには、matchMediaを使います。
function updateFaviconByTheme() {
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
setFavicon(isDark ? "/favicon-dark.png" : "/favicon-light.png");
}
// 初回実行
updateFaviconByTheme();
// テーマ変更を監視
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", updateFaviconByTheme);
SVGファビコンを使えば、ファイル内にCSS @media (prefers-color-scheme: dark) を埋め込んでJavaScript不要でダークモード対応できます。詳しくは以下の記事で解説しています。
Canvas APIで通知バッジを描画する
チャットやメールアプリなどで見かける、ファビコンに未読数のバッジを重ねるテクニックです。Canvas APIで元のファビコンの上にバッジを描画し、それをデータURLに変換してファビコンに設定します。
function setFaviconWithBadge(count: number) {
const canvas = document.createElement("canvas");
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext("2d")!;
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "/favicon-32x32.png";
img.onload = () => {
// 元のファビコンを描画
ctx.drawImage(img, 0, 0, 32, 32);
if (count > 0) {
// バッジの背景(赤い円)
ctx.fillStyle = "#ef4444";
ctx.beginPath();
ctx.arc(24, 8, 8, 0, Math.PI * 2);
ctx.fill();
// バッジの数字
ctx.fillStyle = "#fff";
ctx.font = "bold 10px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(count > 9 ? "9+" : String(count), 24, 8);
}
// データURLに変換してファビコンに設定
setFavicon(canvas.toDataURL("image/png"));
};
}
// 使用例:未読3件のバッジを表示
setFaviconWithBadge(3);
Reactで使う場合はuseEffect内で実行し、クリーンアップ関数でバッジを消す処理を入れるとよいでしょう。
環境別ファビコン(dev / staging / production)
複数の環境を行き来する開発では、タブを見ただけでどの環境かわかると便利です。
// 環境判定してファビコンを切替
const envFavicons: Record<string, string> = {
development: "/favicon-dev.png", // 黄色
staging: "/favicon-staging.png", // オレンジ
production: "/favicon.ico", // 通常のファビコン
};
const env = import.meta.env.MODE; // Viteの環境変数
setFavicon(envFavicons[env] ?? envFavicons.production);
Next.js App Routerの場合は、前述のicon.tsxでprocess.env.NODE_ENVを参照する方法がよりスマートです。
PWA対応(manifest.json / webmanifest)
PWA(Progressive Web App)としてホーム画面追加やインストールに対応する場合、manifest.json(またはsite.webmanifest)にアイコンを定義する必要があります。
基本的なマニフェストファイル
{
"name": "My Application",
"short_name": "MyApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563eb",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
PWAアイコンのポイント
- 最低限必要なサイズは192x192と512x512の2種類。512x512はスプラッシュスクリーンにも使われます
- maskableアイコンは、Android端末で丸型や角丸に切り抜かれても主要部分が見えるよう、アイコンの中心40%の範囲に重要な要素を収めたものです。通常アイコンとmaskableアイコンは別の画像として用意するのが理想ですが、同じ画像にpurposeを付けることもできます
- ファイル拡張子は
.jsonでも.webmanifestでも動作しますが、W3C仕様では.webmanifestが正式です
フレームワーク別のマニフェスト読み込み
| フレームワーク | 設定方法 |
|---|---|
| Next.js (App Router) | public/site.webmanifest を配置し、metadataで<link rel="manifest">を指定 |
| Next.js (Pages Router) | public/site.webmanifest を配置し、_document.tsxの<Head>にlinkタグ |
| React (Vite) | public/site.webmanifest を配置し、index.htmlにlinkタグ |
| React (CRA) | public/manifest.json(デフォルトで生成済み) |
| Nuxt 3 | public/site.webmanifest を配置し、nuxt.config.tsのapp.headにlinkタグ |
Next.js App Routerでのmetadata指定例:
// app/layout.tsx
export const metadata: Metadata = {
manifest: "/site.webmanifest",
};
なお、App Routerにはfavicon.icoと同様にマニフェストファイルも自動認識する機能があります。app/manifest.jsonまたはapp/manifest.tsを配置するだけで、<link rel="manifest">が自動生成されるため、metadataへの記述は不要です。manifest.tsを使えばTypeScriptで動的にマニフェストを生成することもできます。
// app/manifest.ts
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "My Application",
short_name: "MyApp",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#2563eb",
icons: [
{ src: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
{ src: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
],
};
}
これらのアイコンファイルを手作業で用意するのは手間がかかります。一括生成ツールに1枚の画像をアップロードすれば、全サイズのPNG・ICO・マニフェストファイル・HTMLスニペットをZIPで一括ダウンロードできます。

よくある質問(FAQ)
Q. Next.js App Routerでfavicon.icoはpublic/とapp/のどちらに置くべき?
app/ディレクトリを推奨します。 App Routerでは、app/favicon.icoを自動検出して<link>タグを生成する仕組みがあるため、設定コード不要でファビコンが有効になります。public/に置く場合はmetadataAPIで明示的にパスを指定する必要があります。なお、app/にfavicon.icoを置くとpublic/favicon.icoは無視されるので、両方に配置する必要はありません。
Q. Vite + ReactでファビコンがDev環境では表示されるのにビルド後に消える
index.htmlのパスが正しいか確認してください。href="favicon.ico"(相対パス)ではなくhref="/favicon.ico"(ルート相対パス)にしないと、サブパスでデプロイした際にファイルが見つかりません。Viteのbaseオプション(例: base: '/my-app/')を設定している場合でも、index.html内のルート相対パス(/favicon.ico)はViteがビルド時にbaseの値に合わせて自動的にリベースしてくれるため、手動でパスを書き換える必要はありません。
Q. Nuxt 3でnuxt.config.tsとuseHeadの両方でファビコンを設定するとどうなる?
useHeadで設定したlinkタグがnuxt.config.tsの設定を上書き(マージ)します。同じrel属性のタグが重複する可能性があるので、動的に変更する場合はnuxt.config.tsからファビコンのlinkタグを外し、app.vueのuseHeadで一元管理するのがシンプルです。
Q. フレームワークの開発サーバーでファビコンが更新されない
ファビコンはブラウザに強くキャッシュされます。開発中にファビコンを差し替えた場合は、スーパーリロード(Ctrl+Shift+R / Cmd+Shift+R)を試してください。それでも反映されない場合は、シークレットモードで開くかブラウザのキャッシュを完全にクリアします。Next.jsの.next/やViteのnode_modules/.vite/キャッシュを削除してから再起動する方法も有効です。
本番リリース時にも同様のキャッシュ問題は起きます。最も確実な対策は、URLにクエリパラメータ(キャッシュバスター)を付ける方法です。
<link rel="icon" href="/favicon.ico?v=2" />
?v=2のようにバージョンを付けることで、ブラウザに新しいファイルとして強制認識させることができます。ファビコンを更新するたびにこの値を変えてください。
ファビコンが表示されない場合の原因と対処法は、以下の記事で詳しくまとめています。
Q. SVGファビコンはどのフレームワークでも使える?
はい、すべてのフレームワークでSVGファビコンが使えます。<link rel="icon" type="image/svg+xml" href="/favicon.svg">をHTMLに追加するだけです。Next.js App Routerではapp/icon.svgを配置するだけで自動認識されます。SVGファビコンのダークモード対応や作り方の詳細は以下の記事をご覧ください。
まとめ:フレームワーク別ファビコン設定の比較表
| フレームワーク | ファビコン配置場所 | 設定方法 | 動的生成 |
|---|---|---|---|
| Next.js App Router | app/ | ファイル配置で自動認識 / metadata API | icon.tsx (ImageResponse) |
| Next.js Pages Router | public/ | _document.tsx の <Head> にlinkタグ | なし(JSで動的差替は可) |
| React (Vite) | public/ | index.html にlinkタグ | なし(JSで動的差替は可) |
| React (CRA) | public/ | index.html + manifest.json | なし(JSで動的差替は可) |
| Vue.js (Vite) | public/ | index.html にlinkタグ | なし(JSで動的差替は可) |
| Nuxt 3 | public/ | nuxt.config.ts の app.head | useHead でリアクティブに変更可 |
Next.js App Routerが最も設定が簡単で、ファイルを置くだけで完結します。その他のフレームワークではpublic/ディレクトリへの配置とHTML(または設定ファイル)への記述が必要ですが、手順自体はどれもシンプルです。
どのフレームワークを使う場合でも、準備すべきファビコンファイルは共通です。ICO・PNG・SVG・Apple Touch Iconの推奨サイズについてはファビコンのサイズ一覧をご覧ください。
Laravelでのファビコン設定(Blade・Vite・Inertia.js対応)はこちら。
この記事を書いた人
Favicon作成ツール 編集チーム
月間数十万人が利用するWebツールを開発・運営するチームです。ファビコンの作成・変換・設定に関する実践的なノウハウを、現場の経験をもとに発信しています。