scriptでweb fontを読み込む時の方法とかまとめ

目標
TypeSquareやFont Plusなど、scriptでweb fontを読み込むサイトの利用方法があまりまとめられていないので、ざっくりここにまとめて最終的に記事にする
特に動的取得やssr, spaの場合、サイト側が提供するjs apiを駆使しないとダメとなるので、そこも確認しまとめる
対象サイト
- TypeSquare
- Font Plus
対象フレームワーク
- nuxt
- next
- astro
- remix
etc...

Font Plus
プラン(2024/12/4 時点)
無料トライアル= スマートライセンス6ヶ月分
無料トライアルについて無料トライアルは、フォントプラスの「スマートライセンス」を無料でお試しいただけるサービスです。 スマートライセンスは複数サイトにて5000PV(有効期限6か月)まで、ご利用いただけます。
スマートライセンスはこんな感じ
使う文字だけを読み込む効率的なフォント配信方式
が大事で、動的な文字列に対してはフォントが当てられない
詳しくはこちら

Font Plus 使い方
会員登録は必須
マイフォントに利用したいフォントを登録する
スクリプトの発行を開く
スクリプトをサイトに設置する
※cssなどstyleでfont familyを設定する必要がある
nextの場合
layout.tsx
に scriptを貼っつける
<html lang="ja">
<head>
{/* font plusのscript */}
<script src="https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2"></script>
</head>
<body>{children}</body>
</html>
これだけで動くには動くがエラーとなる
Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts
eslintの警告
Nextではパフォーマンスの関係上、<Script>
を利用するか、async
, defer
をscript
に設定する必要がある
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded
ブラウザでのエラー
React のハイドレーション (hydration) プロセス中に、サーバーが生成したHTMLとクライアントが再レンダリングした HTMLが一致ていない
font plusのjsによってdomの操作が行われているのが原因
解決としては <Script>
を利用する or async
を付与して、後から自分たちで明示的に読み込む

scriptの挙動
script
script
にasync
とdefer
をつけた時、つけてない時の挙動
(ChatGPTに尋ねてみた)
わかりやすい記事もあった
なにもつけていない
- ダウンロードタイミング: HTMLの解析中にスクリプトのダウンロードが始まる
- 実行タイミング: スクリプトのダウンロードが完了した時点で、HTMLの解析が一時停止し、スクリプトを即座に実行する
- 影響: HTMLの解析がブロックされるため、ページのロード時間が延びる可能性がある
async
- ダウンロードタイミング: HTMLの解析と並行してスクリプトをダウンロードする。
- 実行タイミング: ダウンロードが完了次第、HTMLの解析を一時停止してスクリプトを即座に実行する。
- 影響: スクリプトの実行順序は ダウンロード完了順 になり、HTMLの解析が中断する可能性がある。
- 用途: 他のスクリプトに依存しない、独立したスクリプト(例: 広告スクリプト、分析ツール)。
defer
- ダウンロードタイミング: HTMLの解析と並行してスクリプトをダウンロードする。
- 実行タイミング: HTMLの 完全な解析が終わった後 に実行される(DOMContentLoaded イベント前)。
- 影響: スクリプトの実行順序は HTMLに記述された順序 が保証される。
- 用途: HTML構造に依存するスクリプト(例: DOM操作スクリプト)。

scriptでなにもつけてない、deferをつけた場合は、前述通りハイドレーションエラーになるがフォントは読み込める
asyncの場合は、明示的に読み込めばフォントも反映された
asyncの場合でも自動で読み込まれるはずだけど、そうでないのはnextだからかもしれない…?
大人しく Script
を使っておくのが良さそう

Script
を使う場合はこんな感じ
// onLoadを利用するには "use client"の記述が必要
<Script
src="https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
strategy="afterInteractive" // default
onLoad={() => {
if (FONTPLUS) {
FONTPLUS.reload(false);
}
}}
/>
strategy
はいくつか種類があるが afterInteractive
(初期値)で良さそう?
試したところどれも自動で読み込んでくれなかったので明示的な読み込みが必要だった
この場合にonLoad
を利用できるafterInteractive
が記述をまとめられるので楽そう
って思ったけどページ遷移時の考慮を考えるとそうでもないかも& use client
の記述必要になって layout.tsx
に配置できないので微妙かも?
これだけだと初回の時しか読み込まれない(=ページ遷移するとフォントが反映されない)
ので、修正
<html lang="ja">
<head>
{/* font plusのscript */}
<Script
src="https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
strategy="beforeInteractive"
/>
</head>
<body>
{children}
{/* font plusを自分たちで明示的に読み込む */}
<FontLoader />
</body>
</html>
"use client";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
export const FontLoader = () => {
const pathname = usePathname();
useEffect(() => {
if (window.FONTPLUS) {
window.FONTPLUS.reload(false);
}
}, [pathname]);
return null;
};
beforeInteractive
でサーバーサイド側であらかじめ読み込んでおいて、パスの変更に応じてクライアント側で読み込む
ページ遷移ごとにPVが発生したり、動的な文字列には対応していない

動的な文字列対応
FONTPLUS.reload(false)
, window.FONTPLUS.reload(false)
を行うだけ
もしくは、見えないように必要な文字列をセットしておけばいけるらしい(漢字とか含めるとすごいことになりそうなので、cdnで読み込めるようにプランを変えた方が良さそう…)
<div style="display:none">ダミー文字0123456HOGEhoge</div>

Nuxt
nextと違ってheadに追加するだけでOK
app: {
head: {
script: [
{
src: "https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2",
},
],
},
},
SSRの場合はこれで反映される
SPAの場合は明示的に読み込む必要あり
onMounted(() => {
FONTPLUS.reload(false);
});
next同様で、ページ遷移や動的な要素には対応していないので別途対応が必要となる

RemixはSPA、SSRともに試してみる

Remix(SPA)
<script
src="https://webfont.fontplus.jp/accessor/script/fontplus.js?XXXXXX%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
></script>
Layout
にscript
を追加する。
Remix(SSR)
<script
src="https://webfont.fontplus.jp/accessor/script/fontplus.js?XXXXXX%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
async
></script>
Layout
にscript
を追加する。async
をつけないとフォントが反映されない
SPA, SSRともに
export default function App() {
const { pathname } = useLocation();
useEffect(() => {
if (window.FONTPLUS) {
window.FONTPLUS.reload();
}
}, [pathname]);
return <Outlet />;
}
topのApp
部分でreload
を実行する

Astor(SSG)
astroの場合は、is:inline
をつけるだけ
<script is:inline src="https://webfont.fontplus.jp/accessor/script/fontplus.js?XXXXXXXX%3D&box=11w4ujahMPo%3D&aa=1&ab=2"></script>
is:inline
をつけないとastroの最適化処理が入ってしまって動かなくなる
SSGなのでページ遷移時のフォント再読み込みは不要
→ ClientRouter
を利用する場合は必要
<script>
const loadFont = () => {
if (window.FONTPLUS) {
window.FONTPLUS.reload(false);
}
};
document.addEventListener('astro:after-swap', loadFont);
</script>
ドキュメントの通りだと astro:page-load
とastro:after-swap
の2種類でルーティングのページアニメーションを検知できるらしい
どちらでも問題ないけど、適切そうなのはastro:after-swap
なので採用

font-smooth について
web fontを適応すると、sampleより表示が太く見える場合がある
これはブラウザやOSによってアンチエイリアスの処理が異なるから
それを調節できるのが font-smoothing
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
基本的にはこれを指定しておけば問題ない

記事にしてみた