Astroでビルド時にGoogle Fontsを最適化するインテグレーションの作り方
Google Fontsは便利ですが、フォントファイルのサイズが大きくなりがちで、ページの読み込み速度に影響を与えることがあります。しかし、Google Fontsには特定の文字列だけを読み込むためのtextパラメータが用意されており、これを活用することでフォントファイルのサイズを大幅に削減できます。
本記事では、Astroのビルドプロセス時に自動的にページ内の文字列を抽出し、Google Fontsのリクエストにtextパラメータを追加するカスタムインテグレーションの実装方法を紹介します。
Google Fontsのtextパラメータについて
Google Fontsでは、クエリパラメータで読み込む文字列を絞り込むことができます。例えば、ページ内で使用している文字が「あいうえお」だけの場合、以下のようにtextパラメータを指定することで、その文字だけを含むフォントファイルを取得できます。
https://fonts.googleapis.com/css2?family=Noto+Sans+JP&text=あいうえおこれにより、全文字セットを含むフォントファイルをダウンロードする必要がなくなり、読み込み時間とデータ転送量を大幅に削減できます。
詳細な仕様については、以下の記事が参考になります。
参考: https://zenn.dev/neriko/articles/3b55c547a07c8d22c9f1
Astroインテグレーションの実装
動的に更新する必要のないページでは、ビルド時にページ内の文字列を抽出してGoogle Fontsのリクエストに追加することで、この最適化を自動化できます。以下は、Astro用のカスタムインテグレーションの実装例です。
インテグレーションのコード
プロジェクトのルートにintegrationsディレクトリを作成し、その中にgoogleFontsOptimizer.tsファイルを作成します。
import * as fs from 'node:fs'
import type { AstroIntegration } from 'astro'import { glob } from 'glob'import { JSDOM } from 'jsdom'
interface GoogleFontsOptimizerOptions {  maxChars?: number  logDetails?: boolean}
export default function googleFontsOptimizer(options: GoogleFontsOptimizerOptions = {}): AstroIntegration {  const { maxChars = 1000, logDetails = true } = options
  return {    name: 'google-fonts-optimizer',    hooks: {      'astro:build:done': async ({ dir, logger }) => {        logger.info('Google Fonts最適化を開始...')
        try {          // HTMLファイルを取得          const htmlFiles = await glob('**/*.html', {            cwd: dir.pathname,            absolute: true          })
          if (htmlFiles.length === 0) {            logger.warn('HTMLファイルが見つかりません')            return          }
          // 全HTMLファイルからテキストノードのみを抽出          const extractedTexts = new Set<string>()
          for (const htmlFile of htmlFiles) {            try {              const htmlContent = fs.readFileSync(htmlFile, 'utf-8')              const dom = new JSDOM(htmlContent)              const document = dom.window.document
              // scriptとstyleタグを除去              const scriptsAndStyles = document.querySelectorAll('script, style')              scriptsAndStyles.forEach((element) => element.remove())
              // body要素のテキストコンテンツのみを取得(alt属性などは除外)              const bodyElement = document.querySelector('body')              if (bodyElement) {                const textContent = bodyElement.textContent || ''
                // 文字ごとに分割してSetに追加                for (const char of textContent) {                  if (char.trim() && extractedTexts.size < maxChars) {                    extractedTexts.add(char)                  }                }              }
              // 最大文字数に達したら処理を終了              if (extractedTexts.size >= maxChars) {                if (logDetails) {                  logger.info(`最大文字数 ${maxChars} に達したため、文字抽出を終了`)                }                break              }            } catch (error) {              logger.warn(`ファイル ${htmlFile} の処理中にエラーが発生: ${error}`)              continue            }          }
          // 抽出された文字列を作成          const optimizedText = Array.from(extractedTexts).join('')
          if (optimizedText.length === 0) {            logger.warn('抽出されたテキストがありません')            return          }
          if (logDetails) {            logger.info(`抽出された文字数: ${optimizedText.length}`)            logger.info(`抽出された全文字: ${optimizedText}`)          }
          // 各HTMLファイルのGoogle Fontsリンクを更新          let processedFiles = 0          for (const htmlFile of htmlFiles) {            try {              const htmlContent = fs.readFileSync(htmlFile, 'utf-8')              const dom = new JSDOM(htmlContent)              const document = dom.window.document
              // Google Fontsのlinkタグを取得              const googleFontsLinks = document.querySelectorAll('link[href*="fonts.googleapis.com/css2"]')              let hasChanges = false
              googleFontsLinks.forEach((link) => {                const href = link.getAttribute('href')                if (!href) return
                // 既にtextパラメータが含まれている場合はスキップ                if (href.includes('&text=') || href.includes('?text=')) {                  return                }
                // textパラメータを追加                const encodedText = encodeURIComponent(optimizedText)                const updatedHref = `${href}&text=${encodedText}`                link.setAttribute('href', updatedHref)                hasChanges = true              })
              // 変更があった場合のみファイルを更新              if (hasChanges) {                const updatedHtml = dom.serialize()                fs.writeFileSync(htmlFile, updatedHtml, 'utf-8')                processedFiles++              }            } catch (error) {              logger.warn(`ファイル ${htmlFile} の更新中にエラーが発生: ${error}`)              continue            }          }
          logger.info(`Google Fonts最適化完了: ${processedFiles}ファイル処理済み`)        } catch (error) {          logger.error(`Google Fonts最適化中にエラーが発生: ${error}`)        }      }    }  }}インテグレーションの仕組み
このインテグレーションは以下の処理を行います。
- ビルド完了時のフック: astro:build:doneフックを使用して、ビルドプロセス終了後に実行されます
- HTMLファイルの取得: globパターンでビルドされたすべてのHTMLファイルを検索します
- テキストの抽出: JSDOMを使用してHTMLをパースし、<script>と<style>タグを除外した上で、body要素のテキストコンテンツから文字を抽出します
- 重複の除去: Setを使用して重複する文字を自動的に除去します
- Google Fontsリンクの更新: 抽出した文字列をURLエンコードして、Google Fontsのリンクにtextパラメータとして追加します
オプションについて
インテグレーションには以下のオプションを指定できます。
- maxChars: 抽出する最大文字数(デフォルト: 1000)
- logDetails: 詳細なログを出力するかどうか(デフォルト: true)
使用方法
必要なパッケージのインストール
まず、必要な依存パッケージをインストールします。
npm install glob jsdomnpm install -D @types/jsdomAstro設定ファイルでの読み込み
astro.config.ts(またはastro.config.mjs)でインテグレーションを読み込みます。
// @ts-checkimport { defineConfig } from 'astro/config';import googleFontsOptimizer from "./integrations/googleFontsOptimizer";
// https://astro.build/configexport default defineConfig({  integrations: [    googleFontsOptimizer(),  ],});オプションを指定する場合は以下のようにします。
googleFontsOptimizer({  maxChars: 500,  // 最大500文字まで抽出  logDetails: false  // 詳細ログを非表示})動作確認
ビルドを実行すると、以下のようなログが出力されます。
astro-pretty-html   HTML formatting...formatted: /Users/User/hoge/dist/index.html✓ HTML formatted successfully in 21ms.13:32:47 [google-fonts-optimizer] Google Fonts最適化を開始...13:32:47 [google-fonts-optimizer] 抽出された文字数: 513:32:47 [google-fonts-optimizer] 抽出された全文字: あいうえお13:32:47 [google-fonts-optimizer] Google Fonts最適化完了: 1ファイル処理済み13:32:47 [build] 1 page(s) built in 4.58s13:32:47 [build] Complete!ビルド後のHTMLファイルを確認すると、Google Fontsのリンクにtextパラメータが追加されていることが確認できます。
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&text=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A..." rel="stylesheet">まとめ
Astroのカスタムインテグレーションを使用することで、ビルド時にGoogle Fontsの最適化を自動化できます。これにより、以下のメリットが得られます。
- フォントファイルのサイズを大幅に削減できる
- ページの読み込み速度が向上する
- 手動でtextパラメータを管理する必要がなくなる
特に、ランディングページやブログなど、コンテンツが静的なサイトでは効果的な最適化手法となります。ぜひ試してみてください。