文章链接发到群里,如果只弹出来一张光秃秃的截图,那博客的品牌建设等于白做。所以我给每篇文章配了自动生成的 OG 分享卡:Next.js 的 next/og 一画,墨水底、橙点字标、中文标题直接渲染成图。
然后我在第一步就摔倒了。
satori 的第一条规矩:不认系统字体
next/og 底层是 satori,把 JSX 渲染成 SVG。它画英文没问题(内置拉丁字体),但要画中文标题,就必须喂给它一份字体文件。微软雅黑就在系统里躺着,它不管。
三条路,全灭
第一条:CDN 拉 Noto Sans CJK。 沙箱里 jsdelivr 的 GitHub 代理直接给我回了个 199 字节的文件——这个字体仓库几个 GB,超出它 gh 代理的 50MB 上限,返回的不是字体,是一句报错。这也算第二课:CDN 的"200 OK"不可信,下载完必须验货。
第二条:GitHub raw 直连。 沙箱出站策略掐掉了 raw.githubusercontent.com,走代理也是半路 reset。16MB 的大家伙在不可靠链路上就是拉不动。
第三条:fontsource 的 npm 包。 @fontsource/noto-sans-sc 装了三次全部超时——它把中日韩字体按 unicode-range 切成上百个小文件来兜底 web font 的按需加载,整个包几十 MB,对这个场景完全是用错了工具。
换个问题:我到底需要多少个汉字?
分享卡上要画的字符,无非是文章标题、摘要、日期、标签。文章是我自己写的中文——GB2312 的 6763 个汉字 + 常用标点 + ASCII,已经覆盖了我这辈子会写进标题的全部字符。
而微软雅黑,就在 C:\Windows\Fonts\msyh.ttc 里。
# 生成 GB2312 全字符集清单
chars = set()
for hi in range(0xA1, 0xF8):
for lo in range(0xA1, 0xFF):
try:
chars.add(bytes([hi, lo]).decode("gb2312"))
except UnicodeDecodeError:
pass
chars |= set(chr(c) for c in range(0x20, 0x7F)) # ASCII
print(len(chars), "chars") # 7543pyftsubset C:/Windows/Fonts/msyh.ttc \
--font-number=0 \
--text-file=og-chars.txt \
--output-file=og-cjk.ttf \
--no-hinting --desubroutinize一条命令,16MB 的 TTC 变成 1.8MB 的 TTF,7543 个字符一个不少。字体进仓库、离线可构建,部署时也不依赖任何外部源。
排查这类问题的通用心法:当"获取完整资源"的所有路都走不通时,把问题从"怎么拿到 100%"改写成"我到底需要哪 5%"。
附赠:satori 的另外两条规矩
字体搞定之后又各摔了一次:
- 多子节点的容器必须显式
display: flex——哪怕只是{author}<span>.</span>这种"文本 + 一个元素"的写法,也直接抛错。 - 别在卡片里放生僻字符——
✦这种装饰符不在字体里,satori 会尝试在线拉"动态字体",沙箱里拉不到,整个响应 500。装饰符全换 ASCII,世界清净了。