Cloudflare Fonts を Reactから使用する
コード置き場
Cloudflare Fonts とは
Cloudflare Fonts は、Google Fonts を使用する場合に、HTML 上の link タグを読み取り、style タグに変換して対象のフォントイメージのアドレスをインライン展開します。またイメージのリンク先は Cloudflare の高速な CDN(Content Delivery Network)を使用しているため、ページロードが高速になります。
ただしこの機能は React の SSR と相性が悪く、出力した HTML 上のタグが書き換えられてしまうため、クライアント側でハイドレーションするときに不整合を起こして、全体が再レンダリングの対象となってしまい、チラツキやパフォーマンスの低下を起こします。
不整合を抑える方法
Cloudflare Fonts が行うのは head タグの中にある、GoogleFont 呼び出しの link タグの削除と style タグの挿入です。そしてハイドレーションエラーになる原因は、link タグが消えてその部分をマウントできないために起こります。追加された style に関しては、仮想 DOM 側に割り当てるものがなくとも華麗にスルーされ、特にエラーにはなりません。つまり link タグだけクライアント側の仮想 DOM から削除すれば問題解決です。
対応コードを作る
フォントの読み込みを制御するための React コンポーネントを作ります。このコンポーネントは、サーバ側では通常通り link タグを生成し、クライアント側では link タグを削除します。ただしローカル環境や Cloudflare Font が有効になっていない場合などに問題が起こらないように、style タグの中を確認して、変換が行われているかどうかを確認しています。
import React from "react";
import { useRef, type FC } from "react";
type FontProperty = {
isLoaded?: boolean;
isData?: boolean;
};
const isServer = typeof window === "undefined";
export const CloudflareFonts: FC<{ href: string | string[] }> = ({ href }) => {
const property = useRef<FontProperty>({}).current;
if (!property.isLoaded && !isServer) {
property.isLoaded = true;
const nodes = document.querySelectorAll("head style[type='text/css']");
property.isData = Array.from(nodes).some((v) =>
v.textContent?.includes("url(/cf-fonts/")
);
}
if (!property.isData) {
const urls = Array.isArray(href) ? href : [href];
return (
<>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
{urls.map((href) => (
<link key={href} rel="stylesheet" href={href} />
))}
</>
);
}
return null;
};
使い方
react router で使う場合の抜粋です。Cloudflare Fonts コンポーネントにフォントのアドレスを指定するだけです。
注意点として、これを有効にするためには、Cloudflare 側のカスタムドメインの設定で Cloudflare Fonts を有効にする必要があります。
import { CloudflareFonts } from "cloudflare-fonts";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<CloudflareFonts
href={[
"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap",
"https://fonts.googleapis.com/css2?family=Kaisei+Decol&display=swap",
"https://fonts.googleapis.com/css2?family=Dela+Gothic+One&display=swap",
]}
/>
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
実行結果
ブラウザ上で要素を確認すると、きちんと変換されたフォントデータが展開されたままになっています。
まとめ
Web Font を利用しつつ、サイトの表示を少しでも高速化したいという場合にこの方法が有効です。
Discussion