Skip to content
yCENzh edited this page Sep 19, 2026 · 1 revision

Fonts

The theme ships with a subsetting pipeline. Full CJK fonts are multiple megabytes; a blog uses a few thousand of their glyphs. Subsetting cuts the download to what your content actually needs.

Turning it on

Two switches, and both have to be on:

// shirones/config/fontConfig.ts
export const fontConfig: FontConfig = {
  mode: "custom",
  subsetting: {
    enable: true,
    includeCommon: true,
    includeContent: true,
    includeI18n: true,
    includeConfig: true,
    allowRemoteText: true,
  },
  budget: { maxFamilyBytes: 4 * 1024 * 1024 },
};
// astro.config.mjs
shirones({ fonts: { subset: true } })

fonts.subset defaults to command === "build", so you usually do not set it at all — but be aware that astro dev serves the full font files. That is deliberate; subsetting scans your content and runs subset-font, which is not something you want on every dev server start. It does mean a glyph can render correctly in dev and as a fallback box in production if the charset scan missed it. See Missing glyphs.

mode has to be "custom". In "system" mode the theme uses the platform font stack and none of this runs.

Only variants with source: "local" are subsetted. Remote fonts are left alone.

What goes into the charset

The scan builds one set of characters from everything the site can render:

Source Toggle What it reads
Common includeCommon ASCII 32–126 plus CJK punctuation and currency symbols
Content includeContent every .md and .mdx under your content directory
i18n includeI18n the theme's ten language dictionaries
Config includeConfig your config modules, your data modules, and the theme's rendered-text overlay
Manual fonts.extraCharacters whatever you pass in

includeConfig is the one people underestimate. Site title, announcement text, nav labels, friend names, project descriptions — all of it is in config or data, not in content. Turning it off to speed up a build produces a subset that is missing your own site's title.

extraCharacters covers what the scan cannot see: glyphs produced by client-side scripts, characters that only appear in a template string built at runtime, an icon font's codepoints.

The collected charset is written to .shirones/fonts/charset.txt on every run. When something is missing, that file is the first thing to check.

Output

Package mode writes subsets into your project:

.shirones/fonts/
├── charset.txt
├── Inter.subset.woff2
├── Inter.stamp
├── NotoSansSC.subset.woff2
└── NotoSansSC.stamp

.shirones/ is generated; deleting it costs you one rebuild. The .stamp files hold the charset the corresponding subset was built from, which is how the cache works.

Building the theme from its own repository writes to src/assets/fonts/.subset/ instead, which is where the repo's tooling and .gitignore expect them.

Caching

A subset is rebuilt only when the charset changes. The stamp file holds the exact charset string; if it matches, the existing .woff2 is reused and the log says so:

[fonts] collected 1818 unique characters
[fonts] Inter: reused cached subset
[fonts] NotoSansSC: 8.42 MB -> 118.3 KB (-98.6%) in 940ms

Editing one post usually changes the charset by nothing at all, so most builds reuse everything.

Remote playlist text

If you use the Meting music widget, song titles and artist names only exist on the remote playlist API. Without them, the subset is missing glyphs that your music sidebar will render.

allowRemoteText: true fetches the playlist and folds its text into the charset. It runs when musicConfig.enable is on and the provider is meting or mixed.

Two things to know:

It happens before the cache check, because the remote text is part of the charset and therefore part of the cache key. With allowRemoteText on, every astro build makes the request. The timeout is eight seconds; a failure logs a warning and continues with the local charset:

[fonts] fetching Meting playlist text: https://…
[fonts] failed to fetch Meting playlist text (fetch failed), continuing with local charset

So an offline build is slower but not broken — it just produces a subset that may be missing song-title glyphs.

It is a network request in a build step. If you build in CI without network access, expect the eight-second stall on every build. Turn it off there and accept the missing glyphs, or leave it on and accept the latency.

Budgets

budget: { maxFamilyBytes: 4 * 1024 * 1024 }

A subset larger than the budget fails the build rather than shipping silently:

[shirones] subset NotoSansSC.subset.woff2 (5242880 bytes) exceeds the family budget (4194304 bytes)

That is a guard against a charset scan gone wrong — a config file that accidentally contains a large block of text will produce an enormous subset, and it is better to hear about it at build time than to find out from a Lighthouse score.

An empty subset also fails, which catches the opposite mistake: a charset scan that collected nothing.

Font roles

The theme expects three roles — body, cjk, mono — configured in fontConfig.fontFamilies. body and cjk are composed into a single sans stack, so Astro's automatic fallback metrics are disabled for them; declaring fallbacks twice produces a visibly wrong line height.

Missing glyphs

A character renders as a fallback box in production but works in dev. Almost always one of:

  1. It is not in the charset. Check .shirones/fonts/charset.txt. If it is missing, find out which source should have supplied it — content, config, i18n — or add it to fonts.extraCharacters.
  2. It arrives at runtime. Anything a client script writes into the DOM after load cannot be scanned. extraCharacters is the only route.
  3. A toggle is off. includeConfig: false is the usual culprit; see the warning above.

Deleting .shirones/fonts/ and rebuilding rules out a stale cache in about ten seconds.

Next

Clone this wiki locally