Load Font Data from Local Files in Astro

Introduction

While attempting to create paths for OGP images using satori in Astro, I encountered the following error when loading font data:

[ERROR] Unsupported OpenType signature /src

# Or, when trying to load with `fs.readFileSync`
ENOENT: no such file or directory, open '../assets/fonts/NotoSansJP-Regular.ttf'

After some research, I found that most people were fetching font data from Google Fonts over the network. However, I wanted to avoid network dependency and load local font files instead. After further investigation, I managed to implement a solution, which I will document here.

Solution

By setting up a plugin in astro.config.mjs as shown below, it became possible to load fonts. This plugin reads font files with specified extensions (.ttf, .woff) as binary data and converts them into an exportable format.

export default defineConfig({
  vite: {
    plugins: [rawFonts([".ttf", ".woff"])],
  },
});

const rawFonts = (ext) => {
  return {
    name: "vite-plugin-raw-fonts",
    transform(_, id) {
      if (ext.some((e) => id.endsWith(e))) {
        const buffer = fs.readFileSync(id);
        return {
          code: `export default ${JSON.stringify(buffer)}`,
          map: null,
        };
      }
    },
  };
}

To load and use the font data, do the following:

import React from 'react';
import satori from 'satori';
import RegularFont from "../assets/fonts/NotoSansJP-Regular.ttf";
import BoldFont from "../assets/fonts/NotoSansJP-Bold.ttf";

const generateOgpImage = async (element: React.ReactNode) => {
  const svg = await satori(
    element,
    {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: "Noto Sans JP",
          data: Buffer.from(RegularFont),
          style: "normal",
          weight: 400,
        },
        {
          name: "Noto Sans JP",
          data: Buffer.from(BoldFont),
          style: "normal",
          weight: 600,
        },
      ],
    }
  );

  // Omitted
};

Conclusion

With this setup, font data can now be loaded from local files. I plan to write another blog post later on how I generated paths for OGP images in Astro.

Related Posts