Google Fontsで学ぶWebフォントの基本 最終回 next/font/googleの詳細設定

next/font/googleのpreloadとsubsetsオプションの関係、「日本語フォントなのにlatinサブセット?」という疑問への回答、そして日本語WebフォントをNext.jsで扱う際の設計判断について解説します。

発行

著者 藤田 智朗 フロントエンド・エンジニア
Google Fontsで学ぶWebフォントの基本 シリーズの記事一覧

前回まで

前回は、next/font/googleの基本的な使い方と、セルフホスティングによる最適化の仕組みを解説しました。フォントの定義から適用、ビルド成果物の確認までを通じて、Next.jsがWebフォントの読み込みをどのように自動化しているかを見てきました。

その中で、フォント定義のコードにsubsets: ['latin']latin=ラテン文字、つまり英数字などの文字体系)という指定がありました。

前回のコード(再掲)

import { Noto_Sans_JP } from 'next/font/google';

export const notoSansJP = Noto_Sans_JP({
  weight: ['400', '700'],
  subsets: ['latin'],
  display: 'swap',
});

日本語フォントを使いたいのに、なぜlatinを指定するのでしょうか。今回は、この疑問を出発点に、preloadsubsetsの関係、そして日本語WebフォントをNext.jsで扱う際の設計判断について解説します。

preloadとsubsetsの関係

next/font/googleにはpreloadsubsetsという2つのオプションがあります。この2つは密接に関連しており、フォントの読み込みパフォーマンスに直接影響します。

preloadとは

preloadは、フォントファイルを<link rel="preload">で先読みするかどうかを指定するオプションです。デフォルトはtrueです。

preloadオプション

const notoSansJP = Noto_Sans_JP({
  weight: ['400', '700'],
  subsets: ['latin'],
  preload: true, // デフォルト
});

preload: trueの場合、Next.jsはHTMLの<head>に次のような<link>タグを自動挿入します。

自動挿入されるpreloadタグ(イメージ)

<link rel="preload" href="/_next/static/media/xxxxx.woff2" as="font" type="font/woff2" crossorigin>

第2回で解説したpreconnectは接続の事前確立でしたが、preloadはさらに踏み込んで、リソースそのものを事前にダウンロードする仕組みです。ブラウザはCSSを解析してフォントの必要性を認識する前に、フォントファイルのダウンロードを開始できます。

subsetsとは

ここで問題になるのが、「どのフォントファイルをpreloadするか」です。

第1回で解説したとおり、Noto Sans JPのような日本語フォントは100以上のサブセットに分割されています。これらすべてをpreloadすると、ページで実際には使わないフォントファイルまでダウンロードしてしまい、かえってパフォーマンスが悪化します。

そこでsubsetsオプションの出番です。subsetsは、preloadの対象となるサブセットを指定します。

subsetsの指定

const notoSansJP = Noto_Sans_JP({
  weight: ['400', '700'],
  subsets: ['latin'], // latinサブセットのみpreload対象
});

この設定では、latinサブセット(英数字や基本的な記号)に対応するフォントファイルだけがpreloadされます。日本語のサブセットはpreloadされませんが、CSSの@font-faceunicode-rangeによる通常のフォント読み込みの仕組みで、ブラウザが必要に応じて取得します。

preload: trueであればsubsetsは省略できない

preload: true(デフォルト)の状態でsubsetsを省略すると、Next.jsはエラーを出します。next buildではビルド時にエラーが発生し、next devではサーバーは起動するもののブラウザでページを開いた際にエラーが表示されます。

エラーメッセージ

`next/font` error:
Preload is enabled but no subsets were specified for font `Noto Sans JP`.
Please specify subsets or disable preloading if your intended subset can't be preloaded.
Available subsets: `cyrillic`, `latin`, `latin-ext`, `vietnamese`

エラーメッセージが示すとおり、preloadが有効なのにどのサブセットをpreloadすべきか指定されていないことが原因です。メッセージには利用可能なサブセットの一覧も表示されます。ここにjapaneseが含まれていない点については、次の節で解説します。

このように、subsetspreloadとセットで機能するオプションなのです。

「日本語なのにlatin?」が起きる背景

subsets: ['latin']という指定に違和感を覚えるのは自然なことです。日本語フォントを使いたいのに、なぜlatinなのか。この疑問に答えるには、subsetsの役割を正確に理解する必要があります。

subsetsは「使えるフォント」を制限しない

ここがもっとも重要なポイントです。subsetsはpreload対象のサブセットを指定するものであり、使用できる文字範囲を制限するものではありません。

subsets: ['latin']と指定しても、日本語のテキストは問題なくNoto Sans JPで表示されます。違いは、latinサブセットのフォントファイルだけが<link rel="preload">で先読みされるという点です。日本語のサブセットは、ブラウザがページ内の日本語テキストを検出した時点で、unicode-rangeに基づいて通常どおり取得されます。

つまり、subsets: ['latin']は「英数字のフォントだけを優先的に先読みし、日本語は通常の読み込みに任せる」という意味です。日本語が使えなくなるわけではありません。

なぜsubsetsにjapaneseがないのか

前節のエラーメッセージに表示されていたAvailable subsetsを改めて見てみましょう。

  • cyrillic
  • latin
  • latin-ext
  • vietnamese

ここにjapaneseがありません。これは、Next.jsが意図的に日本語をpreload可能なサブセットから除外しているためです。

第1回で解説したとおり、日本語フォントは100以上のサブセットに分割されています。もしjapanesepreload対象として指定できたとすると、これら100以上のファイルすべてが<link rel="preload">で先読みされることになります。実際にそのページで使われる日本語の文字が一部のサブセットにしか含まれていなくても、preloadではすべてがダウンロード対象になってしまうため、かえってパフォーマンスが悪化します。

一方、latinサブセットは1〜2ファイル程度です。英数字はほぼすべてのページで使われるため、preloadの効果が高く、ファイル数も少なく無駄がありません。

このような理由から、日本語フォントであってもsubsets: ['latin']を指定するのが適切な設定になります。

補足:Next.js公式ドキュメントでの説明

Next.jsの公式ドキュメントでも、preload可能なサブセットにjapaneseが含まれないケースについて、preload: falseを設定する方法が案内されています。

Noto Sans JP × Next.jsの設計判断

ここまでの内容を踏まえて、Noto Sans JPをNext.jsで扱う際の設計判断を整理します。

基本的な設定

多くのケースで適切な設定は次のようになります。

推奨される基本設定

import { Noto_Sans_JP } from 'next/font/google';

export const notoSansJP = Noto_Sans_JP({
  weight: ['400', '700'],
  subsets: ['latin'],
  display: 'swap',
});

この設定のポイントを整理します。

  • weight: ['400', '700']:使用する太さを明示的に指定する。不要な太さを含めると、その分だけフォントファイルが増える
  • subsets: ['latin']:latinサブセットのみをpreloadする。日本語サブセットはブラウザのunicode-rangeの仕組みに任せる
  • display: 'swap':テキストの可読性を優先する。日本語フォントでレイアウトシフトが気になる場合はoptionalfallbackを検討する

この設定は、latinサブセットだけを先読みしつつ、日本語フォントの読み込みはブラウザのunicode-rangeに任せ、フォント読み込み中もテキストを表示させるという、パフォーマンスと可読性のバランスをとった構成です。

可変フォント(Variable Font)を使う場合

Noto Sans JPは可変フォントにも対応しています。可変フォントを使うと、太さを個別に指定する代わりに、任意の太さを1つのフォントファイルでカバーできます。

可変フォントの場合

import { Noto_Sans_JP } from 'next/font/google';

export const notoSansJP = Noto_Sans_JP({
  subsets: ['latin'],
  display: 'swap',
  // weightを省略すると可変フォントとして読み込まれる
});

すべてのフォントに可変フォント版があるわけではありませんが、Noto Sans JPは可変フォントとして設計されており、Google Fontsでも可変フォント版が提供されています。可変フォント版が提供されているフォントでは、weightを省略すると可変フォント版が使用されます。可変フォントでは1つのファイルで複数の太さをカバーするため、個別のweight指定が不要になります。ただし、サブセットごとのファイルサイズは個別のweightを指定したときより大きくなる場合があります。

補足:可変フォント(Variable Font)について

可変フォントの詳細な解説は、CodeGridの過去記事も参考にしてください。

preload: falseを選ぶべきケース

ここまではpreload: true(デフォルト)を前提に解説してきましたが、あえてpreload: falseを選択すべきケースもあります。

特定のページでしか使わないフォント

サイト全体ではなく、特定のページやコンポーネントでのみ使うフォントの場合、すべてのページでpreloadするのは無駄です。

特定ページ用のフォント

import { Noto_Serif_JP } from 'next/font/google';

export const notoSerifJP = Noto_Serif_JP({
  weight: ['400', '700'],
  subsets: ['latin'],
  preload: false,  // すべてのページでpreloadしない
  display: 'swap',
});

preload: falseにすると、<link rel="preload">は挿入されません。フォントはCSSの@font-faceunicode-rangeの仕組みにより、そのフォントが実際に使われるページでのみ読み込まれます。

装飾的なフォント

見出しの装飾や特定のUIコンポーネントで使う装飾的なフォントは、テキストの可読性に直結しないため、ページの主要コンテンツの表示を遅延させてまでpreloadする必要がないことが多いです。

複数フォントの使用

複数のフォントファミリーを使う場合、すべてをpreloadするとpreload対象のファイルが増え、かえってパフォーマンスに悪影響を及ぼす可能性があります。もっとも重要なフォント(本文用のフォントなど)だけをpreloadし、それ以外はpreload: falseにするのが効果的です。

複数フォントの使い分け

import { Noto_Sans_JP, Noto_Serif_JP } from 'next/font/google';

// 本文用:preloadあり
export const notoSansJP = Noto_Sans_JP({
  weight: ['400', '700'],
  subsets: ['latin'],
  display: 'swap',
  // preload: true(デフォルト)
});

// 見出し用:preloadなし
export const notoSerifJP = Noto_Serif_JP({
  weight: ['400'],
  preload: false,
  display: 'optional',
});

preload: falseであればsubsetsは省略できる

preload: falseの場合、preloadするサブセットを指定する必要がないため、subsetsを省略できます。

preload: falseならsubsets不要

export const notoSerifJP = Noto_Serif_JP({
  weight: ['400'],
  preload: false,
  display: 'swap',
});

これは、subsetspreloadのためのオプションであることを裏付けています。

シリーズのまとめ

今回は、next/font/googlepreloadsubsetsオプションの関係、そしてNoto Sans JPをNext.jsで扱う際の設計判断について解説しました。

  • subsetspreload対象のサブセットを指定するオプションであり、使用できる文字範囲を制限するものではない
  • 日本語フォントでもsubsets: ['latin']が推奨される。日本語の100以上のサブセットをすべてpreloadするのは非効率なため
  • preload: falseは、特定ページでしか使わないフォントや、複数フォントを使い分ける場合に有効
  • preload: falseの場合はsubsetsを省略できる

このシリーズでは全5回を通じて、Webフォントの基本からNext.jsによる最適化まで解説してきました。

第1回〜第3回では、フレームワークを使わない素の構成で、Webフォントが表示されるまでの仕組みを見てきました。@font-faceの役割、Google Fontsの分割配信(unicode-range)、preconnectとCORSの必要性、そしてfont-displayによるフォント読み込み中の表示制御について解説しました。

第4回〜第5回では、Next.jsのnext/font/googleがこれらの仕組みをどのように自動化・最適化しているかを見てきました。セルフホスティングによる外部リクエストの排除、font-display: swapの自動設定、そしてpreloadsubsetsによる読み込みの最適化について解説しました。

Webフォントは「なんとなく使えている」状態になりやすい分野ですが、ブラウザやフレームワークが実際に何をしているかを理解しておくことで、問題が起きたときにDevToolsのネットワークパネルを見て原因を特定し、適切に対処できるようになります。このシリーズがその助けになれば幸いです。