next/font/google で踏む2つの落とし穴——日本語だけ勝手に別フォントになる/ネットのない環境でビルドが落ちる
next/font/google は便利だが、日本語と英数字が混ざる見出しと、ネットにつながらないビルド環境で別々に事故る。欧文用フォントを日本語に当てると日本語だけ勝手に明朝体に変わる問題と、フォントをビルド時にダウンロードする仕様でオフラインだとビルドが失敗する問題。どちらもエラーが出ず原因が見えにくい。
next/font/google は便利だが、日本語と英数字が混ざる見出しと、ネットにつながらないビルド環境で、それぞれ別の形で事故る。どちらもエラーが出ない、または出ても原因が見えにくい。
落とし穴1:欧文用の書体を日本語の見出しに当てると、日本語だけ勝手に明朝体になる
事象
- 見出しに英数字だけ収録した装飾フォント(Didone 系など)を指定したら、英数字はそのフォントで表示されるのに、日本語部分だけ妙に細く、あるいは潰れて見える
- Didone 用に詰めた負の
letter-spacingやfont-weight: 700が、和文で「黒い塊」になる - エラーは出ない。「なんとなく文字バランスが悪い」という見た目の違和感だけが残る
原因
next/font/google で subsets: ['latin'](=ラテン文字、つまり英数字だけ)を指定した書体には、日本語の文字が入っていない。その書体だけを font-family に指定して日本語に当てると、ブラウザは「この書体に日本語が無い」と判断し、日本語部分だけをパソコンやスマホの標準フォントで代わりに表示する。多くの環境でこの標準フォントは明朝体になる。この置き換えはエラーも警告も出さずに起きるので気づきにくい。
そして見出し用に効かせた負トラッキングや極太が、意図しない明朝体のほうにかかる。これが「文字バランスが悪い」の正体だ。
/* ❌ 英数字だけの書体を指定 → 日本語は標準フォント(明朝体)に勝手に置き換わる */
.heading {
font-family: var(--font-didone);
letter-spacing: -0.03em;
font-weight: 700;
}
解決策
font-family を「欧文用の装飾書体 → 和文書体 → 標準フォント」の順に並べる。ブラウザは文字ごとに「その書体に無ければ次の書体を使う」と探すので、英数字は先頭の装飾書体、日本語は次の和文書体で表示され、1 つの見出しの中で両立する。
import { Playfair_Display, Noto_Serif_JP } from "next/font/google";
const didone = Playfair_Display({
subsets: ["latin"],
variable: "--font-didone",
});
const jp = Noto_Serif_JP({
subsets: ["latin"], // 和文サブセットは重いので指定しない
weight: ["400", "700"],
preload: false,
variable: "--font-jp",
});
.heading {
font-family: var(--font-didone), var(--font-jp), serif;
}
- 和文 Web フォントは重いので
preload: falseにして、読み込むのは見出しなど限定箇所だけにする - Didone 用に詰めた負のトラッキングや極太は和文に効かせない。和文には別クラスで通常のウェイト・トラッキングを当てる
落とし穴2:next/font/google はビルド時にフォント本体をダウンロードする → ネットのない環境でビルドが落ちる
事象
ネットにつながらない環境(オフラインの PC、社内ネットワークだけの CI、外部通信を止めたサンドボックスなど)で next build すると、コードは正しいのに次のようなエラーで落ちる。
Module not found: Can't resolve
'...turbopack-next/internal/font/google/font'
ローカルのネットが繋がる環境では再現しない。だから「自分のマシンでは通るのに CI だけ落ちる」という形で出やすい。
原因
next/font/google は、指定した Google Fonts の実体をビルド時にダウンロードして自己ホスト用に取り込む(実行時にユーザーが Google へ取りに行くのではなく、ビルド時に取り込んで配信する方式)。だからビルドマシンが fonts.gstatic.com に到達できないと、フォントを取れず解決失敗になる。ランタイムの遅延ではなくビルドそのものが通らないのがこの問題の性質だ。
解決策
- フォント本体をリポジトリに同梱し、
next/font/localで読む。ビルド時に外部へ出ないので、ネットのない環境でも通る
import localFont from "next/font/local";
const didone = localFont({
src: "./fonts/PlayfairDisplay.woff2",
variable: "--font-didone",
});
- あるいはビルド環境から
fonts.gstatic.comへの通信を許可する(CI の外部通信の許可リストにこのドメインを入れる)
ネットのない環境で回すことが分かっているなら、最初から next/font/local に寄せておくのが確実だ。
補足
- 落とし穴 1 は「フォントの置き換えがエラーも出さずに起きる」のが厄介で、コンソールにも何も出ない。見出しの日本語だけ雰囲気が違うと感じたら、指定した書体の
subsetsに日本語が含まれているかをまず疑う - 落とし穴 2 は「ビルド時に取得して自己ホストする」という仕様を知っていれば一発でわかる。逆に「実行時に Google へ取りに行く」と誤解していると原因にたどり着けない
※ 本記事にはアフィリエイトリンクが含まれます。