如果你曾经在 app/root.tsx 里直接写 favicon <link> 标签,结果被 Remix 去重甚至悄悄丢掉——正确做法是用 links 导出。Remix v2 期望 HTML link 描述符就放在这里,框架会自动把它们渲染到文档 head 里。完整 API 见官方 Remix route links 文档。
前置条件
你需要一个能跑起来的 Remix v2 项目,外加一组 favicon 文件。要凑齐现代浏览器需要的那一整套,建议准备:
/public/favicon.svg— 矢量源文件,支持深色模式/public/favicon.ico— 兼容老浏览器的兜底文件/public/apple-touch-icon.png— 180×180,用于 iOS 主屏幕/public/site.webmanifest— 用于 PWA 安装行为
放进 public/ 的任何东西都会从站点根路径直接对外提供,所以 /public/favicon.svg 实际就是 https://yoursite.com/favicon.svg,不需要额外路由。
步骤 1:在 root.tsx 里写 links 导出
links 导出是一个返回 <link> 描述符数组的函数。Remix 会把每个描述符映射成文档 head 里的真实 HTML 标签。为了拿到正确的类型,从 @remix-run/node(或你用的运行时对应的包)引入 LinksFunction。
import type { LinksFunction } from '@remix-run/node';
export const links: LinksFunction = () => [
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
{ rel: 'alternate icon', href: '/favicon.ico', type: 'image/x-icon' },
];
rel 的取值直接对应 HTML 规范——完整词表见 whatwg 的 rel=icon 定义。浏览器会按顺序挑第一个匹配当前上下文的图标,所以要把清晰的 SVG 排在 ICO 兜底之前。
步骤 2:补上 Apple Touch 和 manifest
不管资源类型是什么,Remix 对每个描述符一视同仁。直接堆到同一个数组里:
export const links: LinksFunction = () => [
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
{ rel: 'alternate icon', href: '/favicon.ico', type: 'image/x-icon' },
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' },
{ rel: 'manifest', href: '/site.webmanifest' },
];
sizes 属性只是描述性的——浏览器在挑选最合适的图标时会参考它。如果你想了解 Remix 导出之外的 favicon link 标签写法,可以看我们的 favicon HTML 代码示例。
步骤 3:优雅地处理深色模式 SVG
浏览器确实会读取 icon link 标签上的 media。你可以准备两个 SVG,让浏览器根据用户的色彩模式自动挑选。把第二个文件存成 /public/favicon-dark.svg,然后加上两个描述符:
{
rel: 'icon',
href: '/favicon.svg',
type: 'image/svg+xml',
media: '(prefers-color-scheme: light)',
},
{
rel: 'icon',
href: '/favicon-dark.svg',
type: 'image/svg+xml',
media: '(prefers-color-scheme: dark)',
},
HTML 规范明确支持 icon link 上的 media 描述符,所以这是一等公民方案,不是野路子。SVG 这一侧如何配置才能让两个文件正确切换,详见我们的 favicon 深色模式指南,里面把媒体查询的写法讲得比较细。
步骤 4:更新图标时打破缓存
浏览器对 favicon.ico 缓存得相当激进,而 Remix 默认不会给 public/ 下的资源加内容哈希。发布新设计时,给 URL 拼一个查询字符串:
{ rel: 'icon', href: '/favicon.svg?v=2', type: 'image/svg+xml' }
每次发布新图标就把 v 加一。绑定 URL 的缓存会跟着刷新,用户标签页立刻就是新图标。
常见坑
通过 Remix 的 links 导出接入图标时,留意下面这些:
- 把标签直接写在 JSX 里。在
root.tsx的<head>里直接写标签虽然能渲染出来,但 Remix 的去重和 SSR 排序可能会悄悄把它们干掉。导出才是文档里的标准路径。 - 漏写
type。没有 MIME 提示时,Safari 有时会挑错格式。image/svg+xml和image/x-icon是稳妥的默认值。 - 使用
shortcut icon。这个遗留属性不在 HTML 规范里——现代浏览器会直接忽略。用alternate icon替代。 - 漏掉 manifest 的
rel。PWA 安装读的是site.webmanifest,不是 icon 链接。要在同一个links数组里引用它。 - 部署后图标还是旧的。浏览器缓存在部署后依然存在。更新图标时务必加缓存破坏参数。
收尾
把 favicon HTML 标签接入 Remix,links 导出是最干净的方式——类型有保障、SSR 输出可预测、换图标时只改一个文件。再搭一个来自 Mzu favicondl 的清晰矢量源,加上缓存破坏查询参数,你的 Remix 应用在每个浏览器标签页里都会显得很专业。