1. ホーム
  2. Next.js

【Next.js】next/font でWebフォントを最適化する方法|Google Fonts・ローカルフォントを解説

Share

Webサイトで好みのWebフォントを使いたいとき、Google Fonts などを外部から読み込むのが一般的ですが、外部サーバーへのリクエストが増えて表示が遅くなったり、フォントの読み込みが終わった瞬間に文字がガクッとずれるレイアウトシフトが起きたりしがちです。Next.js の next/font は、こうした問題を自動で解決してくれるフォント最適化の仕組みです。この記事では、App Router を前提に、next/font/google による Google Fonts の使い方、weightsubsets といった指定、next/font/local でのローカルフォントの読み込み、CSS変数として使う方法まで、初心者〜中級者向けにコード付きで解説します。

next/font とは:フォントを自動で最適化・セルフホストする

next/font は、Next.js に組み込まれているフォント最適化の仕組みです。もっとも大きな特徴は、フォントファイルを自動でセルフホスト(自分のサイトから配信)することです。Google Fonts を使う場合でも、ビルド時にフォントファイルがダウンロードされ、あなたのアプリの他の静的ファイルと一緒に配信されます。その結果、ブラウザから Google のサーバーへのリクエストは一切発生しません。外部への通信が無くなることで、表示速度が改善するだけでなく、ユーザーの情報が外部に送られないというプライバシー面の利点も生まれます。

もうひとつの重要な役割が、レイアウトシフト(CLS)を防ぐことです。Webフォントの読み込みには時間がかかるため、その間はブラウザが用意した代替フォントで文字が表示され、フォントが届いた瞬間に本来のフォントへ切り替わります。このとき2つのフォントで文字の幅や高さが違うと、レイアウトがずれてしまいます。next/font は、CSS の size-adjust などのプロパティを使って代替フォントの見た目の大きさを本来のフォントに近づけることで、この切り替え時のずれをゼロに抑えます。

Google Fonts を使う基本の書き方

Google Fonts を使うときは next/font/google から、使いたいフォント名をそのまま import します。読み込んだフォントは関数になっているので、オプションを渡して呼び出し、その戻り値(フォントオブジェクト)を受け取ります。あとは戻り値の className を、フォントを適用したい要素に渡すだけです。

app/layout.tsx
import { Inter } from "next/font/google";

// フォントを読み込み、インスタンス化する
const inter = Inter({
  subsets: ["latin"], // 使う文字セット(サブセット)
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    // className を適用すると、その要素以下にフォントが効く
    <html lang="ja" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

Inter({ ... }) のようにフォント名の関数を呼び出すことを、この記事では「インスタンス化」と呼びます。戻り値のオブジェクトには className が入っていて、これを要素の className 属性に渡すと、その要素とその子孫にフォントが適用されます。上の例では <html> に渡しているので、ページ全体が Inter フォントで表示されます。フォント名にスペースが含まれる場合(例: Roboto Mono)は、import { Roboto_Mono } from "next/font/google" のようにスペースをアンダースコアに置き換えた名前で読み込みます。

weight・subsets・display の指定

インスタンス化するときに渡すオプションで、フォントの読み込み方を細かく制御できます。よく使うものを表にまとめます。

オプション説明
subsets読み込む文字セット(サブセット)を配列で指定する。例: ["latin"]。必要な文字だけに絞ることでフォントファイルが小さくなる。
weightフォントの太さ。"400" のような文字列、または ["400", "700"] の配列で指定する。非可変フォントでは必須。
style字体。"normal""italic" を指定する。省略時は "normal"
displayフォント読み込み中の表示方法(CSS の font-display に対応)。"swap""optional" などを指定する。既定は "swap"
variableフォントを CSS変数として使うときの変数名を指定する(後述)。

subsets は読み込む文字の範囲を絞るための指定です。ラテン文字だけを使うなら ["latin"] を指定しておくと、フォントファイルが必要な分だけに小さくなり、読み込みが軽くなります。display は、フォントが届くまでの間どう表示するかを決めるもので、既定の "swap" は「まず代替フォントで表示し、届いたら差し替える」動作です。

可変フォントと非可変フォントで weight の扱いが変わる

フォントには、ひとつのファイルで細い〜太いまで連続的に太さを変えられる可変フォント(variable font)と、太さごとに別ファイルになっている非可変フォントがあります。next/font では、この違いによって weight の指定が必要かどうかが変わります。

Inter のような可変フォントを使う場合、weight の指定は省略できます。可変フォントは太さの範囲を丸ごと持っているため、あらためて太さを固定する必要がないからです。一方、Google Fonts の中でも可変フォントに対応していないフォントを使う場合は、weight の指定が必須になります。この場合は、使いたい太さを明示的に渡します。

app/layout.tsx
import { Roboto } from "next/font/google";

// 非可変フォントは weight の指定が必須
const roboto = Roboto({
  weight: ["400", "700"], // 使う太さを配列で指定
  subsets: ["latin"],
});

複数の太さを使いたいときは、上のように weight を配列で渡します。使う可能性のある太さをまとめて指定しておけば、それらすべてが読み込まれます。可変フォントの場合でも weight を指定することはできますが、必須ではないという点が非可変フォントとの違いです。

ローカルフォントを使う

Google Fonts ではなく、自分で用意したフォントファイル(ローカルフォント)を使いたいときは next/font/local を使います。こちらは import localFont from "next/font/local" のようにデフォルトインポートで読み込み、src にフォントファイルのパスを指定します。パスは、この関数を呼び出すファイルからの相対パスで書きます。

app/layout.tsx
import localFont from "next/font/local";

// src にフォントファイルへの相対パスを指定する
const myFont = localFont({
  src: "./fonts/MyFont.woff2",
  display: "swap",
});

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

太さや字体ごとに別のファイルがある場合は、src にオブジェクトの配列を渡し、それぞれのファイルに weightstyle を対応づけます。こうすると、複数のファイルをひとつのフォントとしてまとめて扱えます。

app/layout.tsx
import localFont from "next/font/local";

// ファイルごとに weight / style を対応づける
const myFont = localFont({
  src: [
    { path: "./fonts/MyFont-Regular.woff2", weight: "400", style: "normal" },
    { path: "./fonts/MyFont-Bold.woff2", weight: "700", style: "normal" },
  ],
});

CSS変数として使い、Tailwind などと併用する

ここまでは className を要素に直接渡してフォントを適用してきましたが、フォントをCSS変数として登録しておく方法もあります。variable オプションに --font-inter のような変数名を指定すると、そのフォントを参照する CSS カスタムプロパティが作られます。フォントオブジェクトの variable プロパティを要素の className に渡すと、その要素で CSS変数が使えるようになります。

app/layout.tsx
import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter", // CSS変数名を指定する
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  // inter.variable を渡すと、この要素で --font-inter が使える
  return (
    <html lang="ja" className={inter.variable}>
      <body>{children}</body>
    </html>
  );
}

こうしておくと、CSS 側から var(--font-inter) でフォントを参照できます。Tailwind CSS と組み合わせる場合は、設定ファイルの fontFamily にこの変数を登録しておけば、font-sans のようなユーティリティクラス経由でこのフォントを使えるようになります。className を直接渡す方法と違い、CSS変数にしておくと「本文はこのフォント、見出しは別のフォント」のように、CSS の側で柔軟に使い分けられるのが利点です。

フォントはモジュールのトップレベルで呼び出す

next/font を使うときに必ず守るべき決まりが、フォントの読み込み(インスタンス化)はファイルのトップレベルで行うという点です。つまり、import のすぐ下、コンポーネント関数の外で const inter = Inter({ ... }) のように呼び出します。

app/page.tsx
import { Inter } from "next/font/google";

// OK: モジュールのトップレベル(関数の外)で呼び出す
const inter = Inter({ subsets: ["latin"] });

export default function Page() {
  // NG: コンポーネント関数の内側では呼び出せない
  // const inter = Inter({ subsets: ["latin"] });

  return <p className={inter.className}>こんにちは</p>;
}

コンポーネント関数の内側やイベントハンドラの中など、トップレベル以外で呼び出すことはできません。これは、next/font がビルド時にフォントを解析・最適化する仕組みだからです。トップレベルに書くことで、Next.js はどのフォントをどう読み込むかをビルド時に確定でき、セルフホストやレイアウトシフト対策といった最適化を行えます。もし関数内で呼び出そうとするとエラーになるので、必ずファイルの一番上の階層で呼び出すようにしてください。

まとめ

next/font は、Webフォントを自動でセルフホストし、外部リクエストを無くしてパフォーマンスとプライバシーを改善しつつ、size-adjust などでレイアウトシフトを抑える仕組みです。Google Fonts なら next/font/google からフォント名を import してインスタンス化し、戻り値の className を要素に渡します。subsets で文字セットを絞り、非可変フォントでは weight の指定が必須になる一方、可変フォントでは省略できます。自前のフォントは next/font/localsrc を渡して読み込み、variable オプションを使えば CSS変数として Tailwind などと併用できます。いずれの場合も、フォントの呼び出しはモジュールのトップレベルで行うのが決まりです。App Router でフォントを扱うときは、まず next/font を使うことを検討してみてください。

参考ページ