如何為 Next.js 專案加入 Favicon:2025 完整實作指南
Favicon 對現代網頁應用程式至關重要,它會出現在瀏覽器分頁、書籤、手機主畫面和 PWA 安裝中。Next.js 根據路由配置和功能需求提供多種實作方式。
這份完整指南提供您在 Next.js 專案中建立專業 favicon 系統所需的一切,從基礎設定到進階動態功能。
您將學到:
- Next.js 13+ App Router favicon 實作
- 舊版 Pages Router 相容方法
- 動態 favicon 更新與主題適配
- PWA 和多裝置最佳化
- 效能最佳化與問題排解
- 實際程式碼範例與最佳實務
快速開始:基本 Favicon 設定(5 分鐘)
步驟 1:產生 Favicon 檔案
推薦工具: 使用 RealFaviconGenerator 或 Favicon.io 獲得專業成果。
必要檔案結構:
public/
├── favicon.ico # 通用相容(16x16、32x32)
├── favicon-16x16.png # 瀏覽器分頁(舊版支援)
├── favicon-32x32.png # 高解析度瀏覽器分頁
├── apple-touch-icon.png # 180x180(iOS 主畫面)
├── android-chrome-192x192.png # Android 主畫面
├── android-chrome-512x512.png # PWA 和高解析度顯示
└── site.webmanifest # 漸進式網頁應用程式 manifest
步驟 2:即時基礎設定
零設定方式: 將 favicon.ico 放在 public 目錄中。Next.js 會自動在 /favicon.ico 提供服務。
快速驗證: 訪問 http://localhost:3000/favicon.ico 確認檔案可以存取。
Next.js 13+ App Router 實作
方法 1:Metadata API 配置(推薦)
為什麼選擇這個方法: 型別安全、內建 Next.js 支援、自動最佳化、更好的 SEO。
Next.js App Router 支援基於檔案的 favicon 配置:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Next.js App',
description: 'Amazing Next.js application',
icons: {
icon: '/favicon.ico',
shortcut: '/favicon-16x16.png',
apple: '/apple-touch-icon.png',
},
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
方法 2:專業多裝置配置
適用於需要完整平台支援的正式環境應用程式:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Next.js App',
description: 'Amazing Next.js application',
// 完整 favicon 配置
icons: {
// 主要瀏覽器圖示
icon: [
{ url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
],
// 舊版 ICO 支援
shortcut: '/favicon.ico',
// iOS 主畫面圖示
apple: [
{ url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png' },
],
// Android 和 PWA 圖示
other: [
{
rel: 'icon',
url: '/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
rel: 'icon',
url: '/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png'
},
],
},
// PWA manifest 提供類似 app 的體驗
manifest: '/site.webmanifest',
// 額外的行動裝置最佳化
other: {
'theme-color': '#000000',
'msapplication-TileColor': '#000000',
}
}
方法 3:動態 Favicon 產生
進階用例: 根據使用者情境、環境或應用程式狀態動態產生 favicon。
// app/layout.tsx
import { headers } from 'next/headers'
import type { Metadata } from 'next'
export async function generateMetadata(): Promise<Metadata> {
const headersList = headers()
const userAgent = headersList.get('user-agent') || ''
// 基於環境的 favicon 選擇
const isDevelopment = process.env.NODE_ENV === 'development'
const isMobile = /Mobile|Android|iPhone/i.test(userAgent)
// 動態 favicon 邏輯
let faviconPath = '/favicon.ico'
if (isDevelopment) {
faviconPath = '/favicon-dev.ico' // 開發環境標示
} else if (isMobile) {
faviconPath = '/favicon-mobile.ico' // 行動裝置最佳化版本
}
return {
title: 'My Next.js App',
icons: {
icon: faviconPath,
apple: '/apple-touch-icon.png',
},
// 額外的動態 metadata
other: {
'theme-color': isDevelopment ? '#ff6b6b' : '#000000',
}
}
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
舊版 Pages Router 實作(Next.js 12 及以下)
方法 1:元件層級實作使用 next/head
最適合: 頁面專屬 favicon 或需要在不同路由使用不同 favicon 時。
// pages/_app.tsx
import Head from 'next/head'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<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" />
<meta name="theme-color" content="#000000" />
</Head>
<Component {...pageProps} />
</>
)
}
方法 2:全域實作使用自訂 Document(推薦)
最適合: 應用於所有頁面的全站 favicon 配置。
// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head>
{/* 基本瀏覽器圖示 */}
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<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="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png" />
<link rel="icon" type="image/png" sizes="512x512" href="/android-chrome-512x512.png" />
{/* PWA 和平台配置 */}
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#000000" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-config" content="/browserconfig.xml" />
{/* 額外的 SEO 和行動裝置最佳化 */}
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
進階實作
動態 Favicon 更新
建立自訂 hook 用於動態 favicon 更新:
// hooks/useFavicon.ts
import { useEffect } from 'react'
export const useFavicon = (faviconUrl: string) => {
useEffect(() => {
const link = document.querySelector("link[rel*='icon']") as HTMLLinkElement ||
document.createElement('link')
link.type = 'image/x-icon'
link.rel = 'shortcut icon'
link.href = faviconUrl
if (!document.querySelector("link[rel*='icon']")) {
document.getElementsByTagName('head')[0].appendChild(link)
}
}, [faviconUrl])
}
// 在元件中使用
export default function MyComponent() {
const [theme, setTheme] = useState('light')
useFavicon(theme === 'dark' ? '/favicon-dark.ico' : '/favicon-light.ico')
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
切換主題
</button>
)
}
通知徽章 Favicon
建立帶有 favicon 徽章的通知系統:
// components/NotificationFavicon.tsx
import { useEffect, useRef } from 'react'
interface NotificationFaviconProps {
count: number
originalFavicon?: string
}
export const NotificationFavicon: React.FC<NotificationFaviconProps> = ({
count,
originalFavicon = '/favicon-32x32.png'
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = 32
canvas.height = 32
const img = new Image()
img.onload = () => {
if (!ctx) return
// 繪製原始 favicon
ctx.drawImage(img, 0, 0, 32, 32)
if (count > 0) {
// 繪製通知徽章
ctx.fillStyle = '#ff4444'
ctx.beginPath()
ctx.arc(24, 8, 8, 0, 2 * Math.PI)
ctx.fill()
// 繪製數量文字
ctx.fillStyle = 'white'
ctx.font = 'bold 10px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(count > 9 ? '9+' : count.toString(), 24, 8)
}
// 更新 favicon
const link = document.querySelector("link[rel*='icon']") as HTMLLinkElement ||
document.createElement('link')
link.type = 'image/png'
link.rel = 'shortcut icon'
link.href = canvas.toDataURL()
if (!document.querySelector("link[rel*='icon']")) {
document.getElementsByTagName('head')[0].appendChild(link)
}
}
img.src = originalFavicon
}, [count, originalFavicon])
return null
}
// 使用方式
export default function App() {
const [notifications, setNotifications] = useState(0)
return (
<>
<NotificationFavicon count={notifications} />
<button onClick={() => setNotifications(notifications + 1)}>
新增通知({notifications})
</button>
</>
)
}
主題自適應 Favicon
實作會根據系統主題自動調整的 favicon:
// components/ThemeAdaptiveFavicon.tsx
import { useEffect, useState } from 'react'
export const ThemeAdaptiveFavicon = () => {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
// 檢查系統偏好
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
setIsDark(mediaQuery.matches)
// 監聽變化
const handleChange = (e: MediaQueryListEvent) => {
setIsDark(e.matches)
}
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [])
useEffect(() => {
const faviconUrl = isDark ? '/favicon-dark.ico' : '/favicon-light.ico'
const link = document.querySelector("link[rel*='icon']") as HTMLLinkElement ||
document.createElement('link')
link.type = 'image/x-icon'
link.rel = 'shortcut icon'
link.href = faviconUrl
if (!document.querySelector("link[rel*='icon']")) {
document.getElementsByTagName('head')[0].appendChild(link)
}
}, [isDark])
return null
}
// 在 _app.tsx 或 layout.tsx 中使用
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<ThemeAdaptiveFavicon />
<Component {...pageProps} />
</>
)
}
Web Manifest 配置
建立完整的 web manifest 以支援 PWA:
// public/site.webmanifest
{
"name": "My Next.js App",
"short_name": "NextApp",
"description": "Amazing Next.js application",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#000000",
"background_color": "#ffffff",
"display": "standalone",
"start_url": "/",
"scope": "/"
}
建置時 Favicon 產生
在建置時自動產生 favicon:
// scripts/generate-favicons.js
const sharp = require('sharp')
const fs = require('fs')
const sizes = [
{ size: 16, name: 'favicon-16x16.png' },
{ size: 32, name: 'favicon-32x32.png' },
{ size: 180, name: 'apple-touch-icon.png' },
{ size: 192, name: 'android-chrome-192x192.png' },
{ size: 512, name: 'android-chrome-512x512.png' }
]
async function generateFavicons() {
const inputFile = 'assets/logo.png'
for (const { size, name } of sizes) {
await sharp(inputFile)
.resize(size, size)
.png()
.toFile(`public/${name}`)
console.log(`已產生 ${name}`)
}
// 產生 ICO 檔案
await sharp(inputFile)
.resize(32, 32)
.toFile('public/favicon.ico')
console.log('已產生 favicon.ico')
}
generateFavicons().catch(console.error)
// package.json
{
"scripts": {
"generate-favicons": "node scripts/generate-favicons.js",
"build": "npm run generate-favicons && next build"
}
}
常見問題與解決方案
問題 1:開發環境中 Favicon 未更新
問題: 瀏覽器在開發期間快取舊的 favicon
解決方案:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/favicon.ico',
headers: [
{
key: 'Cache-Control',
value: process.env.NODE_ENV === 'development'
? 'no-cache, no-store, must-revalidate'
: 'public, max-age=31536000, immutable',
},
],
},
]
},
}
module.exports = nextConfig
問題 2:正式環境中 Favicon 遺失
問題: 靜態檔案未正確提供
解決方案:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{
source: '/favicon.ico',
destination: '/favicon.ico',
},
]
},
}
module.exports = nextConfig
問題 3:多種 Favicon 格式未載入
問題: 複雜的 favicon 設定造成衝突
解決方案: 使用優先順序方式:
// components/FaviconManager.tsx
import Head from 'next/head'
export const FaviconManager = () => {
return (
<Head>
{/* 高優先:現代瀏覽器 */}
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
{/* 中優先:PNG 備援 */}
<link rel="icon" type="image/png" href="/favicon-32x32.png" />
{/* 低優先:舊版 ICO */}
<link rel="shortcut icon" href="/favicon.ico" />
{/* 行動裝置專用 */}
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</Head>
)
}
測試您的 Favicon 實作
開發測試檢查清單
- [ ] Favicon 出現在瀏覽器分頁中
- [ ] Favicon 顯示在書籤中
- [ ] 行動裝置「加入主畫面」功能正常
- [ ] PWA 安裝圖示正確
- [ ] 深色/淺色模式適配(如果有實作)
專業測試工具
1. Favicon.im - 即時驗證
- 快速 favicon 擷取和測試
- 跨平台相容性檢查
- 識別遺失的尺寸
- 最適合: 快速驗證和問題排解
2. RealFaviconGenerator Checker - 完整分析
- 詳細的平台專屬測試
- PWA 合規驗證
- 效能建議
- 最適合: 專業審核和最佳化
3. 瀏覽器開發者工具 - 技術除錯
- Network 分頁檢查載入問題
- Console 查看遺失檔案的錯誤
- Application 分頁檢查 manifest
- 最適合: 技術問題排解和效能分析
手動測試步驟
- 清除瀏覽器快取
- 在無痕模式訪問網站
- 在不同裝置上測試
- 驗證書籤外觀
- 測試「加入主畫面」功能
效能最佳化
檔案大小最佳化
# 最佳化 PNG 檔案
pngquant --quality=65-80 --output favicon-optimized.png favicon.png
# 最佳化 ICO 檔案
convert favicon.png -resize 32x32 -colors 256 favicon.ico
HTTP 快取標頭
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/:path(favicon.ico|.*\\.png)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
]
},
}
module.exports = nextConfig
完整實作檢查清單
階段 1:基礎設定
- [ ] 產生 favicon 檔案 使用 RealFaviconGenerator 或 Favicon.io
- [ ] 將檔案放在 public 目錄 並使用正確的命名慣例
- [ ] 選擇實作方法(App Router vs Pages Router)
- [ ] 基本 HTML 設定 包含必要的 link 標籤
- [ ] 測試基本功能 在主要瀏覽器上
階段 2:多裝置最佳化
- [ ] iOS 主畫面支援(180x180 apple-touch-icon)
- [ ] Android 相容性(192x192 和 512x512 圖示)
- [ ] PWA manifest 配置 提供類似 app 的體驗
- [ ] Windows 磚塊支援 包含適當的 meta 標籤
- [ ] 主題色彩整合 用於行動瀏覽器
階段 3:進階功能(選用)
- [ ] 動態 favicon 更新 使用自訂 hooks
- [ ] 主題自適應圖示 支援淺色/深色模式
- [ ] 通知徽章 提供即時更新
- [ ] 效能最佳化 包含快取標頭
- [ ] 建置時產生 使用自動化腳本
關鍵實作策略
現代 Next.js 專案(13+)
推薦: 使用 App Router 搭配 metadata.icons API,獲得型別安全、最佳化的 favicon 管理。
舊版專案(12 及以下)
推薦: 在 _document.tsx 中實作全域覆蓋,搭配 next/head 處理頁面專屬需求。
動態應用程式
進階: 結合靜態設定與執行時更新,使用自訂 hooks 和 canvas 操作。
PWA 應用程式
必要: 包含完整的 manifest 配置,多種圖示尺寸和適當的 meta 標籤。
最終建議
從簡單開始: 先使用基本 ICO + PNG 設定,然後根據需求增強
使用專業工具: RealFaviconGenerator 提供完整覆蓋
徹底測試: 在各種瀏覽器和裝置上驗證,使用 Favicon.im 快速測試
最佳化效能: 實作適當的快取標頭並壓縮 favicon 檔案
規劃未來成長: 設計您的 favicon 系統以支援未來功能,如通知和主題適配
遵循這份完整指南,您將建立一個專業的 favicon 系統,提升使用者體驗、增強品牌識別,並在所有現代裝置和瀏覽器上完美運作。
使用 favicon.im 快速檢查您的 favicon 是否配置正確。我們的免費工具確保您網站的 favicon 在所有瀏覽器和裝置上正確顯示。
免費公共服務
Favicon.im 是一個完全免費的公共服務,受到全球開發者的信賴。