Next13新機能、@next/fontでフォント読み込みを高速化してみた
この記事でやること
- @next/fontの導入
- @next/font導入前後でフォントファイルのリードタイムを比較
以上2点です(PagesRouter環境)
ちなみに@next/fontはGoogleFontsとローカルフォントの2系統に対して使うことができるのですが、今回は両方試してみたいと思います。
実行環境
- bun 1.0.2
- node: v20.6.1
- next: 13.5.1
- react: 18.2.0
- react-dom: 18.2.0
next/fontの概要
まずは@next/fontの概要についてざっくりと。以下、Next公式Docsからの引用です(Google翻訳)
@next/font は、フォント (カスタム フォントを含む) を自動的に最適化し、外部ネットワーク リクエストを削除して、プライバシーとパフォーマンスを向上させます。
@next/font には、任意のフォント ファイル用の組み込みの自動セルフ ホスティングが含まれています。 これは、基礎となる CSS の size-adjust プロパティが使用されているおかげで、レイアウト シフトなしで Web フォントを最適にロードできることを意味します。
この新しいフォント システムでは、パフォーマンスとプライバシーを考慮して、すべての Google フォントを便利に使用することもできます。 CSS とフォント ファイルはビルド時にダウンロードされ、残りの静的アセットと共に自己ホストされます。 ブラウザから Google にリクエストが送信されることはありません。
GoogleFontsをセルフホスティングしてNext側で最適化する、ということでしょうか。
next/font導入
next v13.0.0の頃は@next/font
パッケージのインストールが必要でしたが、現在は必要ないようです(v13.5.1)
create-next-app後、すぐにnext/fontを使用することができます。
GoogleFontsのnext/font対応
まずはGoogleFontsをnext/fontに対応させていきます。
元々は_document.tsx内の<Head>
タグ内でGoogleFontsを読み込んでいましたが、必要ないためこれを削除します。
import Document, { Html, Head, Main, NextScript } from "next/document";
import React from "react";
const document: React.FC<Document> = () => {
return (
<Html className="light">
<Head>
- <link
- href="https://fonts.googleapis.com/css2?-- family=Zen+Kaku+Gothic+Antique:wght@400;700&display=swap"
- rel="stylesheet"
- />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
};
export default document;
そして、_app.tsx内で以下のような記述を行いました。
//~~省略~~
+ import { Zen_Kaku_Gothic_Antique } from "@next/font/google";
+ const ZenKakuGothicNew = Zen_Kaku_Gothic_Antique({
+ display: 'swap',
+ weight: ['400', '700'],
+ preload: false,
+ })
+ const SourceCodePro = Source_Code_Pro({
+ subsets:['latin'],
+ display: 'swap',
+ weight: ['400', '700'],
+ })
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => page);
return getLayout(
<>
+ <style jsx global>{`
+ html {
+ font-family: ${ZenKakuGothicAntique_normal.style.fontFamily},
+ ${ZenKakuGothicAntique_bold};
+ }
+ `}</style>
<Component {...pageProps} />
</>
);
};
export default MyApp;
やっていること
-
GoogleFontsの使用したいフォント名を
@next/font/google
から分割代入でインポートし、constで格納。このとき、weight
とsubset
プロパティを指定しているのがポイント。 -
weight:使いたいfont-weightを指定。variable fontsでないフォントファミリーは、必ずこのweightを指定する必要があります。ちなみに、複数の太さを
['400', '700']
のように、使用したいfont-weightを複数指定することも可能です。 -
subsets: 複数の言語に対応しているフォントファミリー(Robotoなど)の場合、どの言語でサブセット化するかを指定します。日本語フォントのようにsubsetsが必要ない場合は
preload: false
を追記することでsubsetsプロパティをキャンセルできるようです。
ローカルフォントの読み込み
googlefontsと同じく_app.tsx内にローカルフォント読み込みの記述を追加
//~~省略~~
import { Zen_Kaku_Gothic_Antique } from "@next/font/google";
+ import localfont from "@next/font/local";
const ZenKakuGothicAntique_normal = Zen_Kaku_Gothic_Antique({
weight: "400",
subsets: ["japanese"],
});
const ZenKakuGothicAntique_bold = Zen_Kaku_Gothic_Antique({
weight: "700",
subsets: ["japanese"],
});
//pages > KikaiChokokuJISMd.woff
+ const kiChoJIS = localfont({
+ src: "./KikaiChokokuJISMd.woff",
+ variable: "--kicho-jis",
+ });
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => page);
return getLayout(
<>
<style jsx global>{`
html {
font-family: ${ZenKakuGothicAntique_normal.style.fontFamily},
${ZenKakuGothicAntique_bold},
+ ${kiChoJIS.variable}
;
}
`}</style>
<Component {...pageProps} />
</>
);
};
export default MyApp;
これで実装は終わりです。が、ローカルフォントの導入では2点ハマりポイントがありました。
ハマりポイント1:ローカルフォントファイルの置き場所
画像・フォントなどのアセットはpublic/にまとめる慣習があるかと思いますが、@next/fontを使う場合、ローカルフォントのファイルはpagesディレクトリ内でなければならないようです(多分)
public/fonts/ファイル
のような配置ではエラーが発生、buildが失敗しました。
Failed to compile.
Font loader error:
Can't resolve './fonts/KikaiChokokuJISMd.woff' in 'C:\Users\~~~'
Location: pages\_app.tsx
ハマりポイント2:ローカルフォントが反映されない
当初はGoogleFontsと同様の記述で読み込んでいましたが、これだとフォントが反映されませんでした。
これについては、variable
プロパティを指定し、フォントのcss変数を定義することでクリアできました。(詳しくは公式Docsのここを)
//上記完成コードより抜粋。
+ const kiChoJIS = localfont({
+ src: "./KikaiChokokuJISMd.woff",
+ variable: "--kicho-jis", //css変数名を定義する必要がある。
+ });
//~中略~
+${kiChoJIS.variable} //MyApp関数内での呼び出しはこうなる
//~中略~
あとは使いたいCSSファイルで定義した変数名を指定してあげればOK。
.sometext {
font-family: var(--kicho-jis);
}
フォントファイルのリードタイムはどう変わる?
Chrome Devtoolのnetworkから、next/font導入前/後のリードタイムを比較してみたいと思います。
next/font導入前
このような形で、GoogleFontsのリクエストがたくさんあります。(リクエストが多すぎて画像に収まっていないです)
next/font導入後
next/font導入後は、GoogleFontsのリクエストはゼロに!Nextの謳い文句どおりですね。
しかし、ローカルフォントのリクエストは発生しています。
とは言えローカルフォントのリードタイムも導入前291ms -> 導入後243msと50ms程度低くなっていたため、ある程度の効果はある?のではないかと思います(何度か計測しましたが、多少低くなる傾向にあります)
AppRouterでもやってみた(2023/09/21追記)
この記事を公開したのは去年の11月ということもあり、PagesRouter環境での導入方法を紹介しています。
しかしながらv13.4からAppRouterがstableになったということで、今後はあまり価値のない情報になっていくかと思われます。(そもそもNextJSの公式Docs以上に分かりやすい情報は無い)
AppRouterでnext/fontsを導入したリポジトリとサンプルページを公開していますので、大した内容ではありませんがよければご覧ください。サンプルページではGoogleフォントの日本語と英語を使用しており、実際のパフォーマンスを計測することも可能です。
Discussion