Next.js でフォントをサクッと設定しよう

## はじめに

Next.js でフォントの設定をする場合、Google の web font を利用する方法が最も簡単で統率のとりやすい方法の一つである。
この記事では、Next.js で利用できるフォントのタイプとそれぞれの設定方法および使用例を簡潔にお伝えしたい。

## 前提

主要なライブラリは以下である。

- next: 14.2.3(App Router)
- react: 18.3.1

また、OS のバージョンが古い場合は、次に説明する variable fonts を使用できない可能性があるので注意していただきたい。

## フォントのタイプについて

web font には、大きく分けて **variable fonts（可変フォント）** とそうでないフォントの 2 タイプがある。
前者は、フォントの幅・太さ・スタイルごとに個別のフォントファイルを用意するのではなく、それらの組み合わせによるさまざまなバリエーションを一つのファイルに組み込んだものである [^1]。後者は、上記のようなバリエーションごとに単独のファイルが存在する、という違いがある。
両者のこの違いは、パフォーマンスに影響を与える。variable fonts は一つのフォントファイルとして存在するため、variable fonts ではないフォントに比べて HTTP リクエストが少なくて済み、レスポンスも早くなることが期待できる。実は Next.js で今回の方法によりフォントを扱う場合は、フォントファイルの数が幾つだろうと**ビルド時にダウンロードされ [^2]、ランタイムでリクエストされるということはない**らしいので、その意味ではパフォーマンスには差がない。しかし、それでもページがフォントをロードする時間は当然無視できないので、やはり variable fonts に軍配が上がることは想像できる。

[^1]: 2 つのファイルに分割されることもあるらしい（*イタリック*体を含む場合など）
[^2]: 確認したところ、ダウンロードされたフォントファイルは `_next/static/media/` 以下に格納されているようだ。

## 設定

「variable fonts の方がパフォーマンスが出る」ということを紹介しておいて、今回使用したかったのが " じゃない方 " のフォントだったので、それで話を進めさせていただく。
ちなみに、使用したいフォントが variable fonts であるかどうかは、[こちら](https://fonts.google.com/variablefonts) にリストされているかどうかで判別できる。

まずは、`next/font/google` から好きなフォントをインポートし、そのインスタンスを生成する。

```fonts.ts
import { IBM_Plex_Sans_JP } from 'next/font/google';
const myfont = IBM_Plex_Sans_JP({
  subsets: ['latin'],
  weight: ['400', '600'],
});

export { myfont };
```

フォントインスタンス生成時のプロパティの一つである **`weight` は、variable fonts に対しては指定する必要がない**。なぜなら、available な weight をすべて最初からフォントファイルに含んだ形であるからだ。今回はそうではないので指定する必要がある。
単一の指定でも良いし、上記の例のように配列で複数指定することで、それぞれの weight のフォントを利用可能になる。

あとはこのインスタンスを Route layout にて以下のように設定する。

```tsx:layout.tsx
import { myfont } from 'fonts';

export default function Layout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja" className={myfont.className}>
      <body>
        {children}
      </body>
    </html>
  );
}
```

これで、基本的には配下のすべてのコンポーネントにて、設定したフォントで文字が表示されることになる。

## Local fonts

前述した方法は、ビルド時にダウンロードしたフォントを利用するというものであった。
Local fonts の仕組みを用いれば、ビルドよりも前にあらかじめダウンロードしたフォントファイルを指定して利用できるようになるというものがある。

```localfonts.ts
import localFont from 'next/font/local';
const myLocalFont = localFont({
  src: './my-local-font.woff2'
});

export { myLocalFont };
```

Route layout での設定方法は同様のため割愛する。

また、次のようにすると、複数の local fonts を設定できる。

```multiLocalfonts.ts
const roboto = localFont({
  src: [
    {
      path: './Roboto-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './Roboto-Italic.woff2',
      weight: '400',
      style: 'italic',
    },
    {
      path: './Roboto-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
    {
      path: './Roboto-BoldItalic.woff2',
      weight: '700',
      style: 'italic',
    },
  ],
})
```

## フォントが反映されない件について

手元で確認したところ、いくつかの React デフォルト DOM 要素では、フォントが反映されない事象が確認されている。（e.g. `select`, `button`）
きちんとした原因調査はできていないがとりあえずの対処ということで、あらためて各要素に対してフォントを設定してあげると適用された。

```tsx:MyButton.tsx
import { myfont } from 'fonts';

export const MyButton = ({
  onClick,
  className,
}: {
  color?: string;
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
}) => {
  return (
    <button
      onClick={onClick}
      className={myfont.className}
    >
      {'クリック'}
    </button>
  );
};
```

## 参考

https://nextjs.org/docs/app/building-your-application/optimizing/fonts

https://developer.mozilla.org/ja/docs/Web/CSS/CSS_fonts/Variable_fonts_guide