Google Fonts、便利だけど重くないですか?CLSとLCP悪化の元凶に
Webサイトのデザインにおいて、タイポグラフィは非常に重要です。Google Fontsは、高品質なWebフォントを無料かつ手軽に利用できるため、多くのNext.jsプロジェクトで採用されています。
しかし、その手軽さとは裏腹に、パフォーマンスの問題を引き起こす大きな要因にもなり得ます。特に、GoogleのCore Web Vitals(CWV)において、以下の指標に悪影響を与えることが知られています。
- CLS (Cumulative Layout Shift): フォントが読み込まれる前と後でテキストの描画サイズが変わり、レイアウトがガタつく現象。
- LCP (Largest Contentful Paint): フォントファイルがレンダリングをブロックし、メインコンテンツ(多くの場合、テキスト)の表示が遅れる。
「サイトのデザインは良くなったけれど、Lighthouseのスコアが下がってしまった…」そんな経験はありませんか?
従来の“や`@import`ではパフォーマンスの限界
従来、Google Fontsを利用するには、HTMLの“内に“タグを追加するか、CSSファイル内で`@import`ルールを使用するのが一般的でした。しかし、この方法にはいくつかの根本的な問題が潜んでいます。
悩み①: 外部リクエストによるレンダリングブロック
ブラウザは、HTMLを解析中に外部リソース(CSSやフォントファイル)の“タグを見つけると、そのファイルのダウンロードと解析が完了するまでページのレンダリングを一時停止(ブロック)することがあります。このリクエストがGoogleのサーバー(外部ドメイン)に対して行われるため、ネットワークの往復時間(RTT)が余計にかかり、ページの初期表示速度を著しく低下させます。
悩み②: FOUT/FOICによるレイアウトシフト(CLS)
font-display: swap; を指定してレンダリングブロックを回避した場合でも、別の問題が発生します。ブラウザはまず代替フォント(ローカルのゴシック体など)でテキストを描画し(FOUT: Flash of Unstyled Text)、Webフォントのダウンロードが完了した瞬間にフォントを入れ替えます。
このとき、代替フォントとWebフォントの文字幅や高さが異なると、テキスト全体のレイアウトが変動し、CLSスコアが悪化します。
悩み③: 巨大な日本語フォントファイルの読み込み
特に「Noto Sans JP」のような日本語フォントは、数千から数万の文字(ひらがな、カタカナ、漢字)を含むため、フォントファイル全体では数MB(メガバイト)にも達します。サイトで実際に使用している文字が数十種類だとしても、全文字入りの巨大なファイルをダウンロードする必要があり、これはモバイル回線などでは致命的です。
Next.jsの救世主! next/font がすべてを自動で解決します
これらの複雑なWebフォントの問題を、驚くほど簡単かつスマートに解決するために開発されたのが、Next.js 13から導入された`next/font`モジュールです。
next/font とは? 究極のフォント最適化モジュール
next/fontは、Webフォントの読み込みを最適化するためにNext.jsに組み込まれた機能です。Google Fonts(またはローカルフォント)を利用する際に、開発者が面倒な最適化作業を一切行うことなく、最高のパフォーマンスを実現できるように設計されています。
メリット1: 自動セルフホスト(外部リクエストの排除)
next/fontを使用すると、Next.jsはビルド時にGoogle Fontsのサーバーからフォントファイル(.woff2など)を自動でダウンロードします。そして、最適化したフォントファイルを、Webサイトの他の静的アセット(JSやCSS)と一緒に、あなた自身のサーバー(Vercelなど)から配信します。
これにより、以下のメリットが生まれます。
- ブラウザがGoogleのサーバーへ外部リクエストを行う必要がなくなり、ネットワークの往復がゼロになります。
- フォントが自サイトと同じドメインから配信されるため、読み込みが高速化されます。
- 外部へのIPアドレス送信がなくなるため、GDPRなどのプライバシー懸念にも対応できます。
メリット2: 自動サブセット化(ファイルサイズの削減)
これが`next/font`の最も強力な機能の一つです。Google Fonts(特に日本語フォント)を利用する際、`next/font`はビルド時にフォントファイルを解析し、サイトで使用されている文字だけを含む、最小限のフォントファイル(サブセット)を自動的に生成します。
さらに、プリセット(例: subsets: [‘latin’])だけでなく、特定の文字だけを抜き出すことも可能です。日本語フォントに対しても、preloadと組み合わせることで賢く動作し、ファイルサイズを劇的に削減します。
メリット3: CLSの自動防止
next/fontは、読み込んだフォント情報に基づいて、フォントが読み込まれるまでの代替フォントのスタイル(size-adjustなど)をインラインCSSとして自動生成します。これにより、代替フォントで表示されている間も、後から読み込まれるWebフォントとほぼ同じレイアウト(占有領域)を維持することができます。
結果として、フォントが入れ替わる瞬間のレイアウトシフト(CLS)がほぼゼロになり、Core Web Vitalsのスコアが大幅に改善します。
実践! next/font/google の具体的な使い方
では、実際に`next/font`を使ってGoogle Fontsを導入する手順を見ていきましょう。ここでは「Noto Sans JP(日本語)」と「Roboto(英語)」を例にします。
ステップ1: next/font/google をインポートする
まず、フォントを定義したいファイル(例: app/layout.tsx や pages/_app.tsx)で、使用したいフォントをインポートします。
// app/layout.tsx (App Router) または pages/_app.tsx など
import { Noto_Sans_JP, Roboto } from 'next/font/google';
ステップ2: フォントを定義する(Noto Sans JPの例)
インポートした関数を実行し、フォントの設定を定義します。これが`next/font`の核となる部分です。
// Noto Sans JP の設定
const notoSansJp = Noto_Sans_JP({
subsets: ['latin'], // 必要なサブセットを指定(日本語は自動で処理されることが多い)
weight: ['400', '700'], // 使用するウェイト(太さ)を指定
display: 'swap', // FOUTを防ぎつつCLSを最小化する 'swap' が推奨
preload: true, // ビルド時にプリロードする(重要)
variable: '--font-noto-sans-jp', // CSS変数として利用するための名前
});
// Roboto(英語フォント)の設定
const roboto = Roboto({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap',
preload: true,
variable: '--font-roboto',
});
ポイント:
weightに使用する太さを配列で明示的に指定することで、不要なウェイトのフォントファイルがダウンロードされるのを防ぎ、さらなる軽量化が可能です。また、`variable`を指定することで、CSS変数として利用でき、管理が容易になります。
サイト全体に適用する(App Router / Pages Router)
定義したフォントをサイト全体に適用します。`variable`で定義したCSS変数名を使います。
App Router の場合 (`app/layout.tsx`)
<html>タグの`className`に、定義したフォントの`.variable`クラスと、CSS変数を利用するグローバルCSSクラスを渡します。
// app/layout.tsx
import './globals.css';
// (フォント定義は省略)
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
// 複数のフォント変数を結合
<html lang="ja" className={`${notoSansJp.variable} ${roboto.variable}`}>
<body>{children}</body>
</html>
);
}
/* app/globals.css */
body {
/* デフォルトフォントを Noto Sans JP に設定 */
font-family: var(--font-noto-sans-jp), sans-serif;
}
h1, h2 {
/* 見出しだけ Roboto を適用する場合 */
font-family: var(--font-roboto), sans-serif;
}
Pages Router の場合 (`pages/_app.tsx`)
Pages Routerでは、`_app.tsx`でCSS変数をグローバルに適用します。
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { Noto_Sans_JP } from 'next/font/google';
const notoSansJp = Noto_Sans_JP({
weight: ['400', '700'],
subsets: ['latin'],
variable: '--font-noto-sans-jp',
display: 'swap',
preload: true,
});
export default function MyApp({ Component, pageProps }: AppProps) {
return (
// メインのコンポーネントをdivなどで囲み、classNameを適用
<main className={`${notoSansJp.variable}`}>
<Component {...pageProps} />
</main>
);
}
グローバルCSS(例: styles/globals.css)の設定はApp Routerの場合と同じです。
(応用)可変フォント(Variable Fonts)の活用
Google Fontsが可変フォント(Variable Font)に対応している場合、`weight`の配列指定の代わりに、`axes`などを使ってより柔軟に設定することも可能です。可変フォントは、1つのファイルで複数の太さやスタイルを表現できるため、ファイルサイズをさらに削減できる可能性があります。
今すぐあなたのNext.jsプロジェクトに next/font を導入しましょう
next/fontは、従来のWebフォント読み込みが抱えていた「パフォーマンスの低下」「CLSの発生」「巨大なファイルサイズ」といった問題を、ほぼ自動で解決してくれる画期的な機能です。
“タグや`@import`を`next/font`に置き換えるだけで、Core Web Vitalsのスコアが大幅に改善し、ユーザー体験が向上することは間違いありません。特に日本語フォントを扱うプロジェクトでは必須のテクニックと言えるでしょう。
モダンなWeb開発に関するパフォーマンスチューニングや、Next.jsを用いたサイト構築にご興味がありましたら、ぜひお気軽にご相談ください。

