如何為 Next.js 專案加入 Favicon:2025 完整實作指南

Favicon.im

Favicon 對現代網頁應用程式至關重要,它會出現在瀏覽器分頁、書籤、手機主畫面和 PWA 安裝中。Next.js 根據路由配置和功能需求提供多種實作方式。

這份完整指南提供您在 Next.js 專案中建立專業 favicon 系統所需的一切,從基礎設定到進階動態功能。

您將學到:

  • Next.js 13+ App Router favicon 實作
  • 舊版 Pages Router 相容方法
  • 動態 favicon 更新與主題適配
  • PWA 和多裝置最佳化
  • 效能最佳化與問題排解
  • 實際程式碼範例與最佳實務

快速開始:基本 Favicon 設定(5 分鐘)

步驟 1:產生 Favicon 檔案

推薦工具: 使用 RealFaviconGeneratorFavicon.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
  • 最適合: 技術問題排解和效能分析

手動測試步驟

  1. 清除瀏覽器快取
  2. 在無痕模式訪問網站
  3. 在不同裝置上測試
  4. 驗證書籤外觀
  5. 測試「加入主畫面」功能

效能最佳化

檔案大小最佳化

# 最佳化 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

使用 favicon.im 快速檢查您的 favicon 是否配置正確。我們的免費工具確保您網站的 favicon 在所有瀏覽器和裝置上正確顯示。

免費公共服務

Favicon.im 是一個完全免費的公共服務,受到全球開發者的信賴。

15M+
每月 Favicon 請求數
100%
永久免費