Quartz 构建报错 CustomOgImages fetch failed 的完整解决方案

在使用 Quartz 构建站点时,如果启用了 CustomOgImages 插件,可能会遇到类似报错:

 Failed to emit from plugin `CustomOgImages`: fetch failed
     at node:internal/deps/undici/undici:13510:13
     at processTicksAndRejections (node:internal/process/task_queues:105:5)
     at fetchTtf (../util/og.tsx:112:24)
     at ../util/og.tsx:44:18
     at async Promise.all (index 0)
     at async Promise.all (index 1)
     at getSatoriFonts (../util/og.tsx:54:36)
     at Object.emit (../plugins/emitters/ogImage.tsx:116:21)
     at ../processors/emit.ts:24:28
     at async Promise.all (index 10)

这通常是因为 Quartz 默认从 Google Fonts 在线拉取字体,而构建环境无法访问外网或字体 URL 无效。

本文介绍如何改为本地字体加载,彻底解决该问题,并优化构建速度。


🔍 问题原因

  1. Quartz 默认配置:会通过 fetch 从 Google Fonts 下载 .ttf 文件。
   fontOrigin: "googleFonts"
  1. 在 CI/CD 或无法访问 Google 的环境下,这些网络请求会失败,导致 CustomOgImages 插件生成 OG 图片时报错。

🔥 解决方案:改成本地字体加载

1. 修改 Quartz 配置

quartz.config.ts 中将 fontOrigin 改为 local

theme: {
  fontOrigin: "local", // 改为本地字体
  cdnCaching: true,
  typography: {
    header: "Schibsted Grotesk",
    body: "Source Sans Pro",
    code: "IBM Plex Mono",
  },
  ...
}
 

2. 下载字体文件

下载你在 Quartz 配置中使用的字体,保存为 .ttf 文件。

例如你的配置需要:

字体名权重文件名
Schibsted Grotesk400SchibstedGrotesk-Regular.ttf
Schibsted Grotesk700SchibstedGrotesk-Bold.ttf
Source Sans Pro400SourceSansPro-Regular.ttf
Source Sans Pro700SourceSansPro-Bold.ttf
IBM Plex Mono400IBMPlexMono-Regular.ttf
将这些字体文件放入:

static/fonts/ 注意:是在项目根目录下创建static/fonts/文件夹

最终目录:

static/fonts/SchibstedGrotesk-Regular.ttf 
static/fonts/SchibstedGrotesk-Bold.ttf 
static/fonts/SourceSansPro-Regular.ttf 
static/fonts/SourceSansPro-Bold.ttf 
static/fonts/IBMPlexMono-Regular.ttf

3. 修改 fetchTtf 函数

打开 util/og.tsx,找到 fetchTtf 函数,替换为以下内容:

import path from "path"
import { promises as fs } from "fs"
import { FontWeight } from "satori/wasm"
 
/**
 * 改为本地加载字体,不再请求 Google Fonts
 */
export async function fetchTtf(
  rawFontName: string,
  weight: FontWeight,
): Promise<Buffer<ArrayBufferLike> | undefined> {
  const fontMap: Record<string, Record<number, string>> = {
    "Schibsted Grotesk": {
      400: path.join(process.cwd(), "static/fonts/SchibstedGrotesk-Regular.ttf"),
      700: path.join(process.cwd(), "static/fonts/SchibstedGrotesk-Bold.ttf"),
    },
    "Source Sans Pro": {
      400: path.join(process.cwd(), "static/fonts/SourceSansPro-Regular.ttf"),
      700: path.join(process.cwd(), "static/fonts/SourceSansPro-Bold.ttf"),
    },
    "IBM Plex Mono": {
      400: path.join(process.cwd(), "static/fonts/IBMPlexMono-Regular.ttf"),
    },
  }
 
  try {
    const fontPath = fontMap[rawFontName]?.[weight]
    if (!fontPath) {
      console.warn(`⚠️ 未找到字体 ${rawFontName} weight ${weight}`)
      return undefined
    }
    return await fs.readFile(fontPath)
  } catch (err) {
    console.error(`❌ 读取本地字体失败: ${err}`)
    return undefined
  }
}
 

同时可以删除旧的网络 fetch 逻辑:

// const cssResponse = await fetch(...)
// const css = await cssResponse.text()
// const urlRegex = ...
// const fontResponse = await fetch(match[1])
 

4. 重新构建

执行:

npx quartz build --serve

Quartz 现在会直接从本地读取字体文件,CustomOgImages 插件不再依赖网络,构建稳定。