Shirone 提供了一套全自动、类型安全且高性能的自托管字体系统。修改字体无需改动任何 CSS、布局或组件,只需要编辑单一配置文件:src/config/fontConfig.ts。本地 TTF/OTF 可以作为构建期子集化输入,但生产站点只允许交付优化后的 WOFF2。
为了兼顾西文排版、中日韩(CJK)文字显示和代码可读性,系统将全站字体划分为 4 种独立角色:
角色 (role) |
对应 CSS 变量 | 职责与覆盖范围 | 推荐策略 |
|---|---|---|---|
body |
--font-body |
英文字母、阿拉伯数字、拉丁西文基础正文 | 推荐选用西文字形优美、中等字重的字体 |
cjk |
--font-cjk |
汉字、日文平假名/片假名、韩文等 CJK 字符 | 推荐选用字符集全、缺字率低的本地 .woff2 字体 |
mono |
--font-mono |
文章代码块、行内代码、终端界面、数据图表 | 推荐选用可变字重、对齐良好的等宽字体 |
mono-cjk |
--font-mono-cjk |
代码/终端中的 CJK 字符(可选补充) | mono 不含汉字时使用;推荐本地 CJK 等宽字体 |
💡 字体级联机制:西文字体排在前面,中日文字体排在第二位。当遇到英文字符时,优先使用
body字体渲染;当遇到汉字或假名时,因为西文字体无对应字形,浏览器会自动无缝回退到cjk字体显示。代码字体采用同样的顺序:配置
mono-cjk后,栈为mono → mono-cjk(含其自身 fallback)→ 系统等宽;拉丁字符先命中mono,CJK 字符在mono缺字时才落到mono-cjk。
打开配置文件:src/config/fontConfig.ts。
如果你不需要加载任何自定义字体,想直接使用访客设备自带的系统字体(如 macOS 的苹方 / San Francisco、Windows 的微软雅黑 / Segoe UI、Android 的 Roboto):
export const fontConfig: FontConfig = {
// 切换为纯系统字体模式
mode: "system",
// 留空即可,系统不会打包或加载任何字体文件
fontFamilies: [],
subsetting: {
enable: false,
includeContent: false,
includeI18n: false,
includeConfig: true,
includeCommon: true,
// 默认 true:构建期允许抓取 Meting 云端歌单的曲目文本,
// 使歌名里的汉字进入字体子集,避免前台缺字。
// 网络访问的取舍见 docs/performance-guidelines.md。
allowRemoteText: true,
},
budget: {
maxTotalBytes: 8 * 1024 * 1024,
maxFamilyBytes: 5 * 1024 * 1024,
},
};- 准备你的中文字体
.woff2格式文件(例如MiSans-Normal.woff2),放入src/assets/fonts/目录; - 在
fontFamilies中找到role: "cjk"的配置项进行替换:
{
id: "misans-cjk",
family: "MiSans",
role: "cjk",
source: "local",
variants: [
{
file: "src/assets/fonts/MiSans-Normal.woff2",
weight: 400,
style: "normal",
},
],
fallback: ["system-ui", "sans-serif"],
display: "swap",
preload: false,
}- 将西文字体(例如
Inter-Regular.woff2、Inter-Bold.woff2)放入src/assets/fonts/; - 修改
role: "body"的配置:
{
id: "inter-body",
family: "Inter",
role: "body",
source: "local",
variants: [
{
file: "src/assets/fonts/Inter-Regular.woff2",
weight: 400,
style: "normal",
},
{
file: "src/assets/fonts/Inter-Bold.woff2",
weight: 700,
style: "normal",
},
],
fallback: ["ui-sans-serif", "system-ui", "sans-serif"],
display: "swap",
preload: false,
}Fontsource 提供了大量开源西文字体,无需手动下载 .woff2 文件。
- 在终端安装字体包,例如安装 Lato:
pnpm.cmd add @fontsource/lato
- 将
source设为"fontsource":
{
id: "lato-body",
family: "Lato",
role: "body",
source: "fontsource",
variants: [
{ file: "@fontsource/lato/400.css", weight: 400, style: "normal" },
{ file: "@fontsource/lato/700.css", weight: 700, style: "normal" },
],
fallback: ["ui-sans-serif", "system-ui", "sans-serif"],
display: "swap",
preload: false,
}如果你想使用 Fira Code 或其他等宽字体:
- 安装字体包(或将
.woff2放入src/assets/fonts/):pnpm.cmd add @fontsource/fira-code
- 修改
role: "mono":
{
id: "fira-code-mono",
family: "Fira Code",
role: "mono",
source: "fontsource",
variants: [
{ file: "@fontsource/fira-code/400.css", weight: 400, style: "normal" },
{ file: "@fontsource/fira-code/600.css", weight: 600, style: "normal" },
],
fallback: ["ui-monospace", "SFMono-Regular", "Consolas", "monospace"],
display: "swap",
preload: false,
}如果 mono 字体不含汉字(多数西文等宽字体都是如此),可以额外声明 mono-cjk:
- 把 CJK 等宽字体放入
src/assets/fonts/(或使用 Fontsource 包); - 在
fontFamilies中新增一条:
{
id: "mono-cjk",
family: "Mono CJK",
role: "mono-cjk",
source: "local",
variants: [
{ file: "src/assets/fonts/MonoCjk.woff2", weight: 400, style: "normal" },
],
fallback: ["system-ui", "sans-serif"],
display: "swap",
preload: false,
}配置后,代码字体栈变为:
mono → mono-cjk(含其自身 fallback)→ 系统等宽
- 拉丁字符优先命中
mono,mono-cjk的拉丁字形不会抢走英文; mono-cjk只在mono没有对应字形时接管;- 未配置
mono-cjk时,输出与只有mono时完全一致; - CJK 字体的汉字宽度通常是 1em,而等宽字体的 advance 可能是 0.5em、0.55em 或 0.6em,因此不保证「1 个汉字 = 2 个 ASCII 列」;对齐效果取决于两个字体各自的 advance;
- 配置后
mono的 Astro 声明会改用内部变量--font-mono-base,主题再组合出公开的--font-mono完整栈;用户配置不需要关心这个变量,未配置的站点也不受影响。
| 字段 | 类型 | 说明与规范 |
|---|---|---|
id |
string |
该项配置的唯一 ID(英文连字符命名,如 "my-font-body") |
family |
string |
字体的标准 CSS 名称(如 "ZenMaruGothic-Medium"、"Inter"、"JetBrains Mono") |
role |
"body" | "cjk" | "mono" | "mono-cjk" |
字体角色(不可重复,每个角色最多声明一个) |
source |
"local" | "fontsource" |
字体来源:"local"(本地文件)或 "fontsource"(npm 包) |
variants |
Array |
字体变体列表,包含具体的 file 路径、weight 字重、style 风格 |
fallback |
string[] |
兜底回退字体栈,最后必须以通用系统字体(如 sans-serif 或 monospace)结尾 |
display |
"swap" | "optional" | "block" |
字体加载显示策略,推荐默认使用 "swap" |
preload |
boolean |
是否在 <head> 中生成 <link rel="preload"> 预加载关键字体(默认 false) |
budget |
object |
打包体积检查预算,防止误引入十几兆超大字体导致博客首屏缓慢 |
每次修改字体配置或添加本地字体后,请依次运行以下验证命令:
# 1. 检查语法与配置类型(应报告 0 errors)
npx.cmd astro check
# 2. 执行静态编译与字体打包
pnpm.cmd build
# 3. 校验生产产物中的字体格式与体积预算
pnpm.cmd fonts:check为了解决中文字体动辄 10MB~20MB 的首屏体积问题,系统内置了自动化构建期子集化裁剪管道:
pnpm.cmd dev开发环境:自动直连原始全量字体文件。在写文章时,输入的任何生僻汉字与假名都立即实时渲染,零等待、极速 HMR。pnpm.cmd build生产构建:自动触发字体子集化,将全站文字提取并生成专属.subset.woff2。原始 TTF/OTF 只作为构建输入,不得复制到dist/或被生产 CSS 引用。
文字采集器(scripts/fonts/text-collector.mjs)能自动识别 musicConfig.provider:
local本地模式:自动扫描src/data/music.ts中所有的曲目名与艺术家;custom自定义列表:扫描musicConfig.tracks中的所有曲目文字;meting/mixed云端歌单与混合模式:自动请求 Meting 远端歌单接口,将远端歌单中的所有歌曲标题、歌手与专辑文字一并纳入子集提取池中,彻底杜绝远端歌单歌名缺字!
pnpm.cmd fonts:subset子集化由项目依赖中的 HarfBuzz WebAssembly 实现,安装 pnpm 依赖后即可运行;构建机不需要额外安装 Python 或 fontTools。
pnpm.cmd build 在构建结束后自动运行 pnpm.cmd fonts:check。检查器会同时验证:
- 生产 HTML/CSS 不引用远程字体 URL;
- 生产 HTML/CSS 不引用原始
.ttf/.otf; dist/中不存在被意外复制的原始.ttf/.otf(KaTeX 自带字体除外);- 所有实际引用的自定义字体满足
fontConfig.budget的单字体族和总量预算。
因此不能只看网络面板中的一个字体文件,也不能仅检查 CSS。字体优化验收必须以完整生产构建和 fonts:check 结果为准。
- 为什么我的中文字体遇到某些字时会变回默认字体?
- 说明所选中文字体的汉字库不全(例如部分艺术字体仅包含简体常用 3500 字,缺少繁体字或日本汉字)。
- 建议选用 GBK 全字符集或包含超全 CJK 字符的字体(如开源的思源黑体、霞鹜文楷、萝莉体等)。
- 为什么博客里的日文歌名或假名乱码/断层?
- 很多国内制作的艺术中文字体完全没有绘制日文平假名与片假名。
- 如果你的博客包含日文歌曲或番剧页面,请务必确认字体包含 Hiragana (U+3040-U+309F) 与 Katakana (U+30A0-U+30FF) 字形。
- 本地字体可以使用
.ttf或.otf吗?- 可以作为
subsetting.enable: true时的构建输入,但必须放在src/assets/fonts/,并由生产构建转换为.subset.woff2。如果关闭子集化,建议直接提供优化后的.woff2;无论配置如何,生产dist/都不允许包含或引用原始.ttf/.otf。
- 可以作为
- 修改后页面字体没有刷新?
- 运行
pnpm.cmd build重新编译,或在开发模式下刷新浏览器缓存(Ctrl + F5)。
- 运行