你已经把 SvelteKit 路由搭好、调好了 layout,也把 build 部署上线了。结果打开浏览器标签页一看,还是默认的 Svelte 图标——更糟的是干脆一片空白。解决办法通常都藏在同一个文件里:app.html。

app.html 是 SvelteKit 用来在服务端和客户端渲染每一页的 HTML 外壳。根据 SvelteKit 项目结构文档 的说明,这个模板放在 src/ 根目录,是唯一能注入"在每条路由 hydration 后依然保留"标记的地方。这也让它成为放置 favicon <link> 标签的天然位置。

为什么用 app.html 而不用 layout?

你确实可以在 +layout.svelte 的 <svelte:head> 块里加 favicon 标签,大多数路由下都能正常工作。但 app.html 的渲染早于任何 layout 或组件代码,这就带来了两个实打实的好处:

前置条件

你需要准备:

如果还没生成这些文件,可以用 Mzu favicondl 从任意源图一键生成完整尺寸包。

Step-by-step:给 app.html 添加 favicon link 标签

1. 找到 app.html 文件

打开 src/app.html。默认情况下大致长这样:

<!doctype html>
<html lang='en'>
  <head>
    <meta charset='utf-8' />
    <link rel='icon' href='%sveltekit.assets%/favicon.png' />
    <meta name='viewport' content='width=device-width, initial-scale=1' />
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data='hover'>
    <div style='display: contents'>%sveltekit.body%</div>
  </body>
</html>

注意 %sveltekit.assets% 和 %sveltekit.head% 这两个占位符——它们会在构建时被替换。可以保留它们,再在上面或下面加新标签。

2. 替换默认的 favicon 行

脚手架默认只带一个 favicon.png 链接。删掉它,换成一套完整的标签。HTML 规范 把 rel='icon' 定义为声明图标的官方方式,照着用这个属性就行。

<link rel='icon' href='%sveltekit.assets%/favicon.svg' type='image/svg+xml' />
<link rel='icon' href='%sveltekit.assets%/favicon.ico' sizes='32x32' />
<link rel='apple-touch-icon' href='%sveltekit.assets%/apple-touch-icon.png' sizes='180x180' />
<link rel='manifest' href='%sveltekit.assets%/site.webmanifest' />

SVG 放在最前面,现代浏览器会优先选它。ICO 作为老浏览器的回退方案。Apple Touch 和 manifest 放最后,它们针对的是特定平台。

3. 加入深色模式支持

如果你的 SVG 图标在深色标签页下需要换种样式(这年头谁还没用深色主题呢?),可以声明两个按 media 区分的来源。我们在 favicon 深色模式指南 里用的也是这个套路:

<link rel='icon' href='%sveltekit.assets%/favicon-light.svg' type='image/svg+xml' media='(prefers-color-scheme: light)' />
<link rel='icon' href='%sveltekit.assets%/favicon-dark.svg' type='image/svg+xml' media='(prefers-color-scheme: dark)' />

浏览器会读取 media 属性,匹配当前配色方案。

4. 把文件放进 static/

SvelteKit 会把 static/ 下所有内容从站点根路径对外提供。把每个 favicon 文件按标签里引用的原名放进去。一个清爽的结构是:

static/
├── favicon.svg
├── favicon.ico
├── favicon-light.svg
├── favicon-dark.svg
├── apple-touch-icon.png
└── site.webmanifest

5. 重启 dev server

Vite 对 HTML 外壳的缓存非常激进。改完 app.html 后,按 Ctrl+C 停掉 dev server,再跑一次 npm run dev。不然你看到的可能还是旧的标记。

常见坑

把 favicon 标签放进 body。浏览器只在 <head> 里找图标声明,<body> 里的内容一律忽略,哪怕文件名就叫 app.html。如果不生效,第一件事就是检查这个位置。

标签里的文件名和 static/ 不一致。Linux 部署上大小写敏感,favicon.ICO 和 favicon.ico 是两个不同的文件。文件名必须完全对上。

部署后图标还是旧的。浏览器对 favicon 的缓存相当顽固——有时能缓存几周。换文件后看不到更新,可以加个查询字符串或 content hash 再部署。这个套路对 static/ 下任何静态资源都适用。

SVG 属性被双重编码。如果复制来的片段里 type="image/svg+xml" 用的是双引号,贴进 app.html 外壳后 Vite 可能会把它弄坏。HTML 属性里统一用单引号,构建过程就不会出岔子。

收尾

在 SvelteKit 里,app.html 模板是声明 favicon 最干净的位置——它在任何 JavaScript 运行之前就生效,并且会随每个页面响应一起输出。把完整的 SVG + ICO + Apple Touch 这一套放进 static/,再把对应的 <link> 标签接到 <head> 里,从首次绘制开始每个浏览器标签页就能拿到一张清晰的图标。

想更全面地了解 SvelteKit 的 favicon 方案——包括 +layout.svelte 的做法和动态图标——可以看我们的 SvelteKit favicon 指南。如果需要现成的图标文件,Mzu favicondl 可以从任意源图在几秒内生成完整一套。