使用 Fontsource 管理 Web 字体:从手工字体文件到 npm 依赖

8月 9, 2026
Frontend, CSS, ByAI

说明:本文由 Codex 根据公开资料与已脱敏的历史工程实践辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。

适用范围:本文讨论 Vite、Webpack 等能处理 CSS 与静态资源导入的 Web 构建工具。它只适用于 Fontsource 已合法提供的字体;商业字体、私有字体和不在 Fontsource 中的 CJK 字体仍需遵守各自的授权与交付方式。

先说结论#

@fontsource-variable/* 的价值不是“换一种上传字体文件的方法”,而是把字体变成可审查、可锁定版本的 npm 依赖:字体 CSS 在代码中显式导入,构建工具再把实际需要的字体资源带入产物。

这比在每个应用的 public/fonts/ 手工放置同一套 .woff / .woff2 文件更容易集中维护。但它不意味着每个项目都应该加载自定义字体:如果设计系统已经选择系统字体,或组件主题明确要求默认字体,额外引入 Fontsource 只会增加请求与字体回退的复杂度。

Fontsource 对变量字体的推荐方式是安装对应的 @fontsource-variable/<font> 包,并按所需轴导入 CSS;变量字体能用一组文件覆盖多个字重或轴,但具体可用轴仍要以该字体包的文档为准。Fontsource:变量字体

这个方案原本解决什么问题#

手工提交字体文件并非一定错误,但在多应用仓库里常出现以下维护成本:

  • 同一字体被复制到多个 public/fonts/,升级、替换和许可证核对容易遗漏。
  • 页面样式、字体文件和实际使用的字重脱节;删除某个页面后,旧字体资源可能继续留在仓库中。
  • 无法像普通依赖一样通过 package.json 与 lockfile 追踪来源和版本。
  • 应用各自声明 @font-face,导致 fallback、font-display 与字体族名称不一致。

Fontsource 将字体的 CSS、字体资源与许可元数据随 npm 包分发。构建工具解析 CSS 导入后,会把被引用的字体资源输出到构建产物;开发者不需要把二进制字体文件复制进业务目录。Fontsource 的安装文档也明确要求项目使用能够处理 CSS 导入的 bundler,例如 Vite 或 Webpack。Fontsource:安装与导入

一种历史上的集中式结构#

下面是一个经过脱敏的历史结构。它将字体依赖归属到共享设计系统,而不是让每个应用单独上传字体文件:

packages/design-system/
├── package.json
└── styles/
    ├── fonts.css
    └── fonts-mono.css

apps/example/
└── src/styles.css

共享包只声明真正使用的字体:

{
  "dependencies": {
    "@fontsource-variable/montserrat": "^5.2.8",
    "@fontsource-variable/recursive": "^5.2.8"
  }
}

历史实现使用了下面两个 CSS 入口:

/* packages/design-system/styles/fonts.css */
@import "@fontsource-variable/montserrat";

/* packages/design-system/styles/fonts-mono.css */
@import "@fontsource-variable/recursive/mono.css";

应用只导入共享入口,而不直接触碰字体二进制文件:

/* apps/example/src/styles.css */
@import "@workspace/design-system/styles/fonts.css";
@import "@workspace/design-system/styles/fonts-mono.css";

其中 @workspace/design-system 只是示例 workspace alias,应替换为项目实际的包名或相对路径。这个结构的关键不是目录名,而是三条边界:字体依赖只在一个 owner 包中声明、字体 CSS 只从一个入口进入应用、业务组件只消费语义化的 sans / mono 字体约定。

新项目应如何写#

历史代码可以帮助理解结构,但不能不加核对地复制 import 路径。Fontsource V5 将变量字体拆成独立的 @fontsource-variable/* 包,并统一了变量字体的 family name;新项目应查看所选字体当前页面提供的导入生成器。Fontsource V5 迁移说明

例如,只需要 Montserrat 的可变字重时,可以采用更明确的入口:

pnpm add @fontsource-variable/montserrat
/* styles/fonts.css */
@import "@fontsource-variable/montserrat/wght.css";

@theme {
  --font-sans: "Montserrat Variable", ui-sans-serif, system-ui, sans-serif;
}

随后,Tailwind CSS v4 的 font-sans 会使用这个字体栈;没有加载成功或浏览器不支持时,仍会回退到系统字体。Montserrat 当前的 Fontsource 页面给出的变量 family name 是 Montserrat Variable,并提供 wght.css 入口。Montserrat 的安装说明

若需要等宽字体,先检查该字体实际支持的轴。以 Recursive 为例,它提供 MONOCASLCRSVslntwght 等轴;现代实现应根据所需行为选择生成器给出的 CSS 入口,而不是默认导入全部轴。Recursive 的变量轴

与 HeroUI Pro 的关系#

Fontsource 和 HeroUI Pro 解决的是不同问题:

需求负责者
选择、托管和构建自定义字体资源Fontsource 或拥有授权的字体交付方式
组件如何消费字体、颜色、圆角、阴影等设计 tokenHeroUI / HeroUI Pro 主题
可视化调整字体、颜色、圆角和阴影并导出主题 CSSHeroUI Pro Theme Builder

HeroUI Pro 的 Theme Builder 能管理字体等设计系统参数,但它不等同于一个通用字体授权或字体文件仓库。Pro 主题中,Glass 与 Mouve 都保持默认 typography;只有主题明确包含字体加载时,才会为它引入自定义 web font。HeroUI Pro Theming

因此,使用 HeroUI Pro 时可以按下面的规则决策:

场景是否使用 Fontsource
使用 HeroUI 默认、Mouve 或 Glass,且没有明确品牌字体需求否,直接继承主题默认字体
某个已批准的品牌落地页需要独立字体可以,但限制在该品牌边界内
全站设计系统明确决定替换 sans / mono 字体可以;将 Fontsource 导入和字体 token 一起放到唯一的共享主题入口
使用 Fontsource 未收录的商业字体不可以用 Fontsource 替代;按许可证自托管或使用授权服务

若项目使用 HeroUI Pro,CSS 顺序应先导入 Tailwind、HeroUI OSS 样式、Pro 样式,再导入有意覆盖主题字体的自定义 CSS:

@import "tailwindcss";
@import "@heroui/styles";
@import "@heroui-pro/react/css";

/* Only when the design system intentionally owns a custom font. */
@import "./fonts.css";

不要为了“统一”而让每个 app 自己导入 Fontsource,也不要在 HeroUI 的 Mouve / Glass 主题上无理由覆盖字体。这两种做法都会把原本应该由设计系统控制的视觉决策分散到应用层。

性能与维护检查表#

  1. 优先变量字体;不要同时导入多个静态字重和相同字体的变量版本。Fontsource 也建议在可用时使用变量字体,尤其是需要多个字重的项目。
  2. 只导入实际需要的轴与 italic 样式;不要因为方便而默认导入 full.css
  3. 保留可靠 fallback,例如 ui-sans-serifsystem-uiui-monospace。字体加载失败不应让文本不可读。
  4. 在浏览器 DevTools 的 Network 面板确认只请求预期的 woff2 资源;在 Computed 面板确认最终 font-familyfont-weight 生效。
  5. 在慢网条件下检查首屏:文本应先以 fallback 可读,字体完成后不应造成不可接受的布局跳动。
  6. 每次升级 Fontsource 或替换字体前,重新核对字体名称、支持的轴与许可证元数据;不要把字体文件“来自 npm”误解为无需确认授权。

什么时候不该使用 Fontsource#

以下情况不应为了复用旧方案而引入它:

  • 产品没有品牌字体需求,系统字体已满足可读性与性能目标。
  • 设计系统或 UI 主题已经规定默认 typography,额外字体会破坏视觉一致性。
  • 字体不在 Fontsource 的公开可分发范围内,或许可证不允许当前部署方式。
  • 只为一个极小的装饰文本加载整套字体;此时应评估系统字体、图片资产或更小范围的授权方案。

参考资料#

本文共 2618 字,上次修改于 Aug 9, 2026,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

相关文章

» 零宽度空格与 CSS 换行行为

» 理解和使用 CSS 自定义属性(CSS 变量)

» 了解下自定义数据属性 data-*

» 使 HTML 元素居中有哪些方案?

» CSS Modules 是什么