如何在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
相关产品推荐
相关产品推荐

