You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js 14/15中根据明暗模式动态切换Favicon

Next.js 14 App Router 动态切换Favicon(跟随配色模式)

一、自动跟随系统配色方案(无需手动切换)

这种场景下,直接利用Next.js 14的Metadata配置,结合CSS媒体查询即可实现自动切换,无需额外客户端逻辑。

在app/layout.tsx中配置icons字段,为不同配色模式指定对应Favicon:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  icons: {
    icon: [
      {
        url: '/favicon-black.png',
        media: '(prefers-color-scheme: light)',
        type: 'image/png'
      },
      {
        url: '/favicon-white.png',
        media: '(prefers-color-scheme: dark)',
        type: 'image/png'
      }
    ]
  }
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh-CN">
      <body>{children}</body>
    </html>
  )
}

说明

  • 将你的黑白Favicon文件放在项目根目录的public文件夹下,比如public/favicon-black.png和public/favicon-white.png。
  • 浏览器会自动根据系统当前的配色方案,匹配对应的media规则加载Favicon,无需额外干预。

二、支持用户手动切换配色模式

如果你的应用允许用户手动切换深色/浅色模式(不局限于系统偏好),需要借助客户端组件动态修改Favicon,因为Metadata是服务端渲染的,无法响应客户端状态变化。

步骤1:创建客户端组件处理Favicon切换

在app/components/DynamicFavicon.tsx中编写如下代码:

'use client'

import { useEffect, useState } from 'react'

export default function DynamicFavicon() {
  // 获取当前配色模式:优先取本地存储的手动设置,无则取系统偏好
  const getCurrentScheme = () => {
    const savedScheme = localStorage.getItem('colorScheme')
    if (savedScheme) return savedScheme
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
  }

  const [colorScheme, setColorScheme] = useState(getCurrentScheme())

  useEffect(() => {
    // 更新Favicon的核心逻辑
    const updateFavicon = (scheme: string) => {
      const targetUrl = scheme === 'dark' ? '/favicon-white.png' : '/favicon-black.png'
      const faviconLink = document.querySelector("link[rel*='icon']") as HTMLLinkElement
      if (faviconLink) faviconLink.href = targetUrl
    }

    updateFavicon(colorScheme)

    // 监听系统配色变化(仅当无手动设置时跟随)
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
    const handleSystemChange = (e: MediaQueryListEvent) => {
      if (!localStorage.getItem('colorScheme')) {
        setColorScheme(e.matches ? 'dark' : 'light')
      }
    }
    mediaQuery.addEventListener('change', handleSystemChange)

    // 监听手动切换的自定义事件
    const handleManualChange = (e: CustomEvent) => {
      const newScheme = e.detail
      setColorScheme(newScheme)
      localStorage.setItem('colorScheme', newScheme)
    }
    window.addEventListener('colorSchemeChange', handleManualChange as EventListener)

    // 清理监听
    return () => {
      mediaQuery.removeEventListener('change', handleSystemChange)
      window.removeEventListener('colorSchemeChange', handleManualChange as EventListener)
    }
  }, [colorScheme])

  return null
}

步骤2:在根布局中引入组件

修改app/layout.tsx,添加默认Favicon并引入客户端组件:

import type { Metadata } from 'next'
import DynamicFavicon from './components/DynamicFavicon'

export const metadata: Metadata = {
  icons: {
    icon: '/favicon-black.png' // 设置默认Favicon
  }
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh-CN">
      <body>
        <DynamicFavicon />
        {children}
      </body>
    </html>
  )
}

步骤3:主题切换按钮触发事件

在你的主题切换组件中,触发自定义事件通知Favicon更新:

'use client'

export default function ThemeToggle() {
  const toggleTheme = () => {
    const currentScheme = localStorage.getItem('colorScheme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
    const newScheme = currentScheme === 'dark' ? 'light' : 'dark'
    
    // 触发自定义事件
    window.dispatchEvent(new CustomEvent('colorSchemeChange', { detail: newScheme }))
    // 同步更新HTML的主题类(如果你的样式依赖此)
    document.documentElement.classList.toggle('dark', newScheme === 'dark')
  }

  return (
    <button onClick={toggleTheme}>切换主题</button>
  )
}

注意事项

  • 确保Favicon文件存放在public目录下,Next.js会自动识别并提供静态访问。
  • 仅跟随系统配色时,优先使用第一种方案,实现更简洁且无需客户端脚本。
  • 手动切换场景下,需保证自定义事件的触发与监听逻辑一致,避免状态不同步。

内容的提问来源于stack exchange,提问作者Sabbir Sobhani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 13:10:22