お雑煮研究会

好きな焼肉は魚

Astro Font API で必要な字形だけに最適化された Google Font を使う

CJK 圏ではフロントエンドのパフォーマンス改善において、しばしば「フォントのサブセット化」を行うことがある。

これは、「一部のみデザインの都合でこのフォントを利用したい」という場合に、そこで使われている字形だけを含むように最適化したフォントファイルを利用することでパフォーマンスの劣化を抑える手法だ。 ( フォントのライセンス条項をきちんと確認しないと違反してしまう可能性があるため事前にチェックすること )

アプローチとしては、主に以下の二択が考えられる。

  • 自前でサブセット化したフォントファイルを配信する
  • Google Font の最適化機能を利用する

自前での用意は pyftsubset などのソフトウェアを組み合わせると自動化できるものの、 CI / CD との統合にもそれなりに手間がかかる、というつらさがある。

github.com

一方で Google Font の最適化機能を利用する場合は、リクエスト時に特定のクエリパラメータをつけるだけでよいのでかなり手軽に済む。

developers.google.com

具体的には HTML に含まれる <link> タグの内容を正しく指定すれば最適化されたフォントファイルが手に入ることになる。

現代においては大規模な Web サイト / アプリではフレームワークを用いることが多いので、この機能をフレームワークと統合できたら嬉しそうだ。

Astro Font API との統合

Astro 5.7.0 で追加された Astro Font API によって、ユーザーは Google Font をはじめとした Web Font プロバイダからフォントを取得し、フレームワークと統合されたインターフェースで扱えるようになった。

docs.astro.build

このように設定ファイルで利用したいフォントを定義しておくと、自動的に最適化されたフォントが取得され、事前に指定した CSS 変数経由でフォントを利用できるようになる。

import { defineConfig, fontProviders } from "astro/config";

export default defineConfig({
  experimental: {
    fonts: [{
      provider: fontProviders.google(),
      name: "Roboto",
      cssVariable: "--font-roboto"
    }]
  }
});
---
import { Font } from 'astro:assets';
---

<!-- cssVariable prop には TypeScript の補完が効く -->
<Font cssVariable='--font-roboto' preload />

<style>
body {
    font-family: var(--font-roboto);
}
</style>

そして、Astro 5.7.5 で取り込まれた変更により、冒頭で触れた Google Font のサブセット化機能が Astro Font API に統合された。

github.com

例として、このように glyphs オプションを指定すると、自動的に"おはよう" という字形だけが含まれる Noto Sans JP フォントが利用できるようになる。

import { defineConfig, fontProviders } from "astro/config";

export default defineConfig({
  experimental: {
    fonts: [
      {
        cssVariable: "--font-mini-noto-sans-jp",
        name: "Noto Sans JP",
        provider: fontProviders.google({
          experimental: {
            glyphs: {
              "Noto Sans JP": ["おはよう"],
            },
          },
        }),
      },
    ],
  },
})

実際にアプリケーションをビルドしてみると、 2.2KB という非常に小さなフォントファイルが生成されていることがわかる。

$ ls -lh dist/_astro/fonts
Permissions Size User         Date Modified Name
.rw-r--r--@ 2.2k sushichan044  5 9  17:14   98a181601b3d6ada.woff2

生成された HTML の先頭にはこのような <style> タグが注入されており、事前に指定した CSS 変数を通じて最適化されたフォントを気軽に利用できた。

<style>
  @font-face {
    font-family: "Noto Sans JP-61eeab963b6def3e";
    src: url("/_astro/fonts/98a181601b3d6ada.woff2") format("woff2");
    font-display: swap;
    font-weight: 400;
    font-style: normal;
  }
  @font-face {
    font-family: "Noto Sans JP-61eeab963b6def3e fallback: Arial";
    src: local("Arial");
    font-display: swap;
    font-weight: 400;
    font-style: normal;
    size-adjust: 197.6219%;
    ascent-override: 58.6979%;
    descent-override: 14.5733%;
    line-gap-override: 0%;
  }
  :root {
    --font-mini-noto-sans-jp:
      "Noto Sans JP-61eeab963b6def3e",
      "Noto Sans JP-61eeab963b6def3e fallback: Arial", sans-serif;
  }
</style>

実運用における注意点

便利な機能だが、すべてを解決するわけではなくいくつか注意点があるので触れておく。

  • 事前に必要な字形がわかっていない場合においてはあまり効力を発揮しない。相性が良いのは UI 要素や固定見出しなどに美しいフォントを利用したいケース
  • 事前に指定していない字形が混じった場合は表示が崩れるので注意すること。
    • これについては、特定のフォントを使いたい文言はすべて JavaScript 定数として管理したうえでコンポーネントと設定ファイルの両方から import するなどでカバーできる
  • 記事を書いている途中で気づいたのだが、フォントファイルのキャッシュ処理にバグがある。複数のビルド間で node_modules/.astro/fonts 以下を使いまわしている場合は削除すれば治る。
    • github.com
    • 2025.12.20 追記: 内部で利用しているライブラリ側のバグであり、v0.7.1 で修正しましたが、まだ withastro/astro には取り込まれていなそうです
    • 2026.01.13 追記: Astro 5.16.7 以上に更新すると修正後の挙動になります

まとめ

ということで、 Astro 5.7.5 以降では、Astro Font API に特定のオプションを指定することで、事前に指定した字形だけを含むように最適化された Google Font を扱えるようになった。 相性のいいユースケースにおいては便利なので積極的に利用していきたい。

なお、この機能の実現には unifontというライブラリが使われている。 実は少し前、自分が別の目的で「Google Fonts のサブセット化機能を unifont から扱えるようにする」という対応を入れたことがあった。

github.com

そのときは単に「個人的にサブセット化を使いたい」という動機で作ったのだが、結果的にその仕組みが Astro Font API に取り込まれ、より多くの開発者が便利に使えるようになった。

過去に自分が実装した小さな改善が、思わぬかたちで普段遣いしているフレームワークに恩恵をもたらしているのを見て、少し嬉しい気持ちになっている。

おわり

おわり