既存プロジェクトとデザインシステムを統一するため、内部開発のアプリのフォントをInterからNoto Sans JPに変更する必要がありました。
最初はGoogle Fonts CDN方式を使用していましたが、実装を進める中で2つの課題が浮かび上がりました。
1つ目は、**CSP(Content Security Policy)の設定が複雑化**していたこと。外部フォントサービスのドメインを許可リストに追加する必要があり、セキュリティ設定の管理が煩雑でした。
2つ目は、**外部サービスへの依存**です。Google Fontsがダウンのときにフォントが読み込めないリスクや、通信遅延がページロード時間に影響を与える懸念がありました。
そこで、Next.jsの`next/font`によるセルフホスティング方式に移行しました。実装してみると、これは想像以上にシンプルで、パフォーマンスとセキュリティの両面で大きな改善が実現できました。
## こんな人におすすめ
この記事は以下のような方に役立ちます。
- Next.js で日本語フォント(Noto Sans JP)をセットアップしたい方
- Google Fonts CDN への外部依存を減らしたい方
- Tailwind CSS v4 を使用していて、フォント設定を統合したい方
- CSP(Content Security Policy)を厳しく設定している方
- フォント周りのパフォーマンス最適化に関心のある方
## 要件
- InterフォントをNoto Sans JPに置き換え
- Google Fonts CDNへの外部リクエストを削除
- Tailwind CSS v4の`@theme`ブロックとの統合
- CSP(Content Security Policy)の最適化
## 実装方針
1. `next/font/google`からNoto Sans JPをインポート
2. フォント設定オブジェクトを作成(weights, display, variable)
3. `<html>`要素にCSS変数を適用
4. `<body>`要素に`font-sans`クラスを追加
5. `globals.css`からCDN importを削除し、`@theme`ブロックを更新
6. `next.config.ts`のCSPから外部フォントドメインを削除
## ポイント
### next/fontの設定
```tsx
import { Noto_Sans_JP } from 'next/font/google';
const notoSansJP = Noto_Sans_JP({
subsets: ['latin'], // 最小限のプリロード(日本語は自動で含まれる)
weight: ['400', '500', '600', '700'],
display: 'swap', // FOUT防止
variable: '--font-noto-sans-jp',
});
// htmlにCSS変数を適用
<html lang="ja" className={notoSansJP.variable}>
```
### Tailwind CSS v4との統合
```css
@theme {
--font-sans: var(--font-noto-sans-jp), -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
```
### CSPの簡素化
セルフホスティングにより、以下のドメイン許可を削除できた:
- `https://fonts.googleapis.com`(style-src)
- `https://fonts.gstatic.com`(font-src)
## 学び
- `next/font`はビルド時にフォントをダウンロードし、自動的に最適化・セルフホスティングする
- `subsets: ['latin']`は最小限のプリロード設定。日本語文字はnext/fontが自動的に処理する
- CSS変数を使うことで、Tailwind CSSのユーティリティクラスとシームレスに統合できる
- 外部フォントサービスへの依存を削除することで、CSPがシンプルになりセキュリティが向上する
## 得られた効果
この実装により、以下の改善が実現できました。
**即時の効果:**
- **CSP ルールの削減**: Google Fonts に関連する 2 つのドメイン許可を削除できました
- `https://fonts.googleapis.com`(style-src)
- `https://fonts.gstatic.com`(font-src)
- **セキュリティの強化**: 外部サービスへの依存がなくなり、セキュリティポリシーがシンプルになりました
**パフォーマンス面での改善:**
- **First Contentful Paint(FCP)の改善**: フォントがビルド時にダウンロードされるため、ページロード時の外部通信が削減されました
- **安定性の向上**: Google Fonts サービスの可用性に依存しなくなり、確実にフォントが読み込まれるようになりました
**開発効率の向上:**
- セルフホスティングにより、フォント設定が一箇所に集約されて管理が楽になりました
- Tailwind CSS との統合がシームレスで、デザインシステムの保守性が向上しました
## トラブルシューティング
### フォントが正しく読み込まれない
**現象:** ビルド後にフォントが適用されず、フォールバックフォントが表示される
**原因と解決策:**
1. **subsetsの指定ミス**
```javascript
// ❌ 誤った指定
subsets: ['japanese'] // この指定は不要
// ✅ 正しい指定
subsets: ['latin'] // 日本語は自動で含まれる
```
2. **環境変数の未設定**
```bash
# 環境変数が正しく設定されているか確認
echo $NODE_ENV # production であることを確認
# 開発環境ではnext devで確認
npm run dev
```
3. **キャッシュの問題**
```bash
# Next.jsキャッシュをクリア
rm -rf .next
npm run build
```
### CSS変数が適用されない
**現象:** Tailwind CSSのfont-sansが適用されない
**解決策:**
1. **html要素のクラス確認**
```html
<!-- html要素にfontクラスが適用されているか確認 -->
<html lang="ja" class="font-sans">
```
2. **globals.cssの@themeブロック確認**
```css
@theme {
--font-sans: var(--font-noto-sans-jp), -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
```
### ビルドサイズが大きくなる
**現象:** フォントファイルのためにビルドサイズが増加する
**解決策:**
1. **必要なweightのみを指定**
```javascript
const notoSansJP = Noto_Sans_JP({
weight: ['400', '500', '700'], // 実際に使用するweightのみ
// ...
});
```
2. **preloadの無効化(必要に応じて)**
```javascript
const notoSansJP = Noto_Sans_JP({
display: 'swap',
variable: '--font-noto-sans-jp',
preload: false, // 明示的にpreloadを無効化
});
```
### FOUT(Flash of Unstyled Text)が発生する
**現象:** フォント読み込み中に一時的にフォールバックフォントが表示される
**解決策:**
1. **displayオプションの確認**
```javascript
const notoSansJP = Noto_Sans_JP({
display: 'swap', // FOUTを許容して読み込み速度を優先
// display: 'block' // 完全な読み込みを待つ場合(遅延が発生)
});
```
2. **font-display CSSの確認**
カスタムCSSでフォントの表示動作を調整:
```css
@font-face {
font-family: 'Noto Sans JP';
font-display: swap;
}
```
## よくある質問(FAQ)
### Q1. Google Fontsとセルフホスティング、どちらを選ぶべきですか?
**A:** プロジェクトの要件に応じて選択してください:
**Google Fontsを選ぶべきケース:**
- プロトタイプや開発初期段階
- 多言語対応が必要でフォントファイルサイズが大きい場合
- CDN経由でフォントを配信する利便性を重視する場合
**セルフホスティングを選ぶべきケース:**
- セキュリティポリシー(CSP)を厳しく設定している場合
- 外部サービスへの依存を排除したい場合
- パフォーマンス最適化を重視する場合
- オフライン環境でのフォント表示が必要な場合
### Q2. サブセット(subsets)の指定は必要ですか?
**A:** 基本的には不要です。`subsets: ['latin']` を指定するだけで、`next/font` が自動的に日本語文字を含めてくれます。日本語のみのプロジェクトでも `latin` サブセットを指定することがベストプラクティスです。
### Q3. フォントファイルのサイズはどのくらいになりますか?
**A:** フォントのweight(太さ)の数によって異なりますが、一般的に:
- **1つのweight**: 約 200-400KB
- **4つのweight**(400, 500, 600, 700): 約 1-1.5MB
- **すべてのweight**: 約 3-4MB
実際のサイズは `npm run build` 後の `.next` フォルダ内で確認できます。
### Q4. セルフホスティングすると、本当にパフォーマンスが向上しますか?
**A:** はい、以下の理由でパフォーマンスが向上します:
- **First Contentful Paint (FCP) の改善**: フォントがビルド時にバンドルされるため、ページロード時の外部リクエストが削減されます
- **TTFBの改善**: 外部CDNへの接続時間がなくなるため
- **キャッシュ効率**: ユーザーがサイトを再訪問した際、ブラウザキャッシュからフォントが読み込まれます
ただし、初回ロード時のファイルサイズが増加するため、キャッシュ戦略が重要になります。
### Q5. 複数のフォントを同時にセルフホスティングできますか?
**A:** はい、複数のフォントを同時に使用できます。例えば:
```typescript
import { Noto_Sans_JP } from 'next/font/google';
import { Noto_Serif_JP } from 'next/font/google';
const notoSansJP = Noto_Sans_JP({ ... });
const notoSerifJP = Noto_Serif_JP({ ... });
// Tailwind CSSでそれぞれのフォントを定義
@theme {
--font-sans: var(--font-noto-sans-jp), sans-serif;
--font-serif: var(--font-noto-serif-jp), serif;
}
```
### Q6. next/fontはNext.js 13以降でも使用できますか?
**A:** はい、Next.js 13以降(App Router)でも使用できます。設定方法は基本的に同じですが、フォントファイルの最適化がさらに強化されています。
### Q7. フォントのライセンスはどうなっていますか?
**A:** Noto FontsはApache License 2.0で提供されており、商用利用も可能です。詳細は[Google FontsのNoto Sans JPページ](https://fonts.google.com/noto/specimen/Noto+Sans+JP)で確認してください。
### Q8. セルフホスティングのデメリットはありますか?
**A:** 主なデメリットは以下の通りです:
- **ビルドサイズの増加**: フォントファイルがバンドルされる
- **バージョン管理**: フォントの更新時に手動で対応が必要
- **CDNとのトレードオフ**: キャッシュ戦略を考慮する必要がある
## まとめ
Next.jsの`next/font`を使ったNoto Sans JPのセルフホスティングは、シンプルな設定でパフォーマンスとセキュリティの両面で大きなメリットをもたらします。
**実装のポイント:**
1. `subsets: ['latin']`で最小限のプリロード
2. CSS変数を使った柔軟なフォント管理
3. Tailwind CSS v4の`@theme`ブロックとの統合
4. CSPの簡素化によるセキュリティ向上
**得られる効果:**
- パフォーマンスの向上(FCP改善、外部通信削減)
- セキュリティの強化(外部依存の排除)
- 開発効率の向上(設定の一元化)
この手法は、日本語フォントを使用するNext.jsプロジェクトであればほぼそのまま適用できる汎用的なソリューションです。プロジェクトの要件に合わせて、ぜひ試してみてください。
## 参考リンク
- [Next.js Font Optimization Documentation](https://nextjs.org/docs/app/building-your-application/optimizing/fonts)
- [Google Fonts Noto Sans JP](https://fonts.google.com/noto/specimen/Noto+Sans+JP)
- [Tailwind CSS v4 Documentation](https://tailwindcss.com/docs/v4)
- [Web Font Loading Best Practices](https://web.dev/font-loading/)
既存プロジェクトとデザインシステムを統一するため、内部開発のアプリのフォントをInterからNoto Sans JPに変更する必要がありました。
最初はGoogle Fonts CDN方式を使用していましたが、実装を進める中で2つの課題が浮かび上がりました。
1つ目は、CSP(Content Security Policy)の設定が複雑化していたこと。外部フォントサービスのドメインを許可リストに追加する必要があり、セキュリティ設定の管理が煩雑でした。
2つ目は、外部サービスへの依存です。Google Fontsがダウンのときにフォントが読み込めないリスクや、通信遅延がページロード時間に影響を与える懸念がありました。
そこで、Next.jsのnext/fontによるセルフホスティング方式に移行しました。実装してみると、これは想像以上にシンプルで、パフォーマンスとセキュリティの両面で大きな改善が実現できました。
こんな人におすすめ
この記事は以下のような方に役立ちます。
- Next.js で日本語フォント(Noto Sans JP)をセットアップしたい方
- Google Fonts CDN への外部依存を減らしたい方
- Tailwind CSS v4 を使用していて、フォント設定を統合したい方
- CSP(Content Security Policy)を厳しく設定している方
- フォント周りのパフォーマンス最適化に関心のある方
要件
- InterフォントをNoto Sans JPに置き換え
- Google Fonts CDNへの外部リクエストを削除
- Tailwind CSS v4の
@themeブロックとの統合
- CSP(Content Security Policy)の最適化
実装方針
next/font/googleからNoto Sans JPをインポート
- フォント設定オブジェクトを作成(weights, display, variable)
<html>要素にCSS変数を適用
<body>要素にfont-sansクラスを追加
globals.cssからCDN importを削除し、@themeブロックを更新
next.config.tsのCSPから外部フォントドメインを削除
ポイント
next/fontの設定
import { Noto_Sans_JP } from 'next/font/google';
const notoSansJP = Noto_Sans_JP({
subsets: ['latin'], // 最小限のプリロード(日本語は自動で含まれる)
weight: ['400', '500', '600', '700'],
display: 'swap', // FOUT防止
variable: '--font-noto-sans-jp',
});
// htmlにCSS変数を適用
<html lang="ja" className={notoSansJP.variable}>
Tailwind CSS v4との統合
@theme {
--font-sans: var(--font-noto-sans-jp), -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
CSPの簡素化
セルフホスティングにより、以下のドメイン許可を削除できた:
https://fonts.googleapis.com(style-src)
https://fonts.gstatic.com(font-src)
学び
next/fontはビルド時にフォントをダウンロードし、自動的に最適化・セルフホスティングする
subsets: ['latin']は最小限のプリロード設定。日本語文字はnext/fontが自動的に処理する
- CSS変数を使うことで、Tailwind CSSのユーティリティクラスとシームレスに統合できる
- 外部フォントサービスへの依存を削除することで、CSPがシンプルになりセキュリティが向上する
得られた効果
この実装により、以下の改善が実現できました。
即時の効果:
- CSP ルールの削減: Google Fonts に関連する 2 つのドメイン許可を削除できました
https://fonts.googleapis.com(style-src)
https://fonts.gstatic.com(font-src)
- セキュリティの強化: 外部サービスへの依存がなくなり、セキュリティポリシーがシンプルになりました
パフォーマンス面での改善:
- First Contentful Paint(FCP)の改善: フォントがビルド時にダウンロードされるため、ページロード時の外部通信が削減されました
- 安定性の向上: Google Fonts サービスの可用性に依存しなくなり、確実にフォントが読み込まれるようになりました
開発効率の向上:
- セルフホスティングにより、フォント設定が一箇所に集約されて管理が楽になりました
- Tailwind CSS との統合がシームレスで、デザインシステムの保守性が向上しました
トラブルシューティング
フォントが正しく読み込まれない
現象: ビルド後にフォントが適用されず、フォールバックフォントが表示される
原因と解決策:
-
subsetsの指定ミス
// ❌ 誤った指定
subsets: ['japanese'] // この指定は不要
// ✅ 正しい指定
subsets: ['latin'] // 日本語は自動で含まれる
-
環境変数の未設定
# 環境変数が正しく設定されているか確認
echo $NODE_ENV # production であることを確認
# 開発環境ではnext devで確認
npm run dev
-
キャッシュの問題
# Next.jsキャッシュをクリア
rm -rf .next
npm run build
CSS変数が適用されない
現象: Tailwind CSSのfont-sansが適用されない
解決策:
-
html要素のクラス確認
<!-- html要素にfontクラスが適用されているか確認 -->
<html lang="ja" class="font-sans">
-
globals.cssの@themeブロック確認
@theme {
--font-sans: var(--font-noto-sans-jp), -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
ビルドサイズが大きくなる
現象: フォントファイルのためにビルドサイズが増加する
解決策:
-
必要なweightのみを指定
const notoSansJP = Noto_Sans_JP({
weight: ['400', '500', '700'], // 実際に使用するweightのみ
// ...
});
-
preloadの無効化(必要に応じて)
const notoSansJP = Noto_Sans_JP({
display: 'swap',
variable: '--font-noto-sans-jp',
preload: false, // 明示的にpreloadを無効化
});
FOUT(Flash of Unstyled Text)が発生する
現象: フォント読み込み中に一時的にフォールバックフォントが表示される
解決策:
-
displayオプションの確認
const notoSansJP = Noto_Sans_JP({
display: 'swap', // FOUTを許容して読み込み速度を優先
// display: 'block' // 完全な読み込みを待つ場合(遅延が発生)
});
-
font-display CSSの確認
カスタムCSSでフォントの表示動作を調整:
@font-face {
font-family: 'Noto Sans JP';
font-display: swap;
}
よくある質問(FAQ)
Q1. Google Fontsとセルフホスティング、どちらを選ぶべきですか?
A: プロジェクトの要件に応じて選択してください:
Google Fontsを選ぶべきケース:
- プロトタイプや開発初期段階
- 多言語対応が必要でフォントファイルサイズが大きい場合
- CDN経由でフォントを配信する利便性を重視する場合
セルフホスティングを選ぶべきケース:
- セキュリティポリシー(CSP)を厳しく設定している場合
- 外部サービスへの依存を排除したい場合
- パフォーマンス最適化を重視する場合
- オフライン環境でのフォント表示が必要な場合
Q2. サブセット(subsets)の指定は必要ですか?
A: 基本的には不要です。subsets: ['latin'] を指定するだけで、next/font が自動的に日本語文字を含めてくれます。日本語のみのプロジェクトでも latin サブセットを指定することがベストプラクティスです。
Q3. フォントファイルのサイズはどのくらいになりますか?
A: フォントのweight(太さ)の数によって異なりますが、一般的に:
- 1つのweight: 約 200-400KB
- 4つのweight(400, 500, 600, 700): 約 1-1.5MB
- すべてのweight: 約 3-4MB
実際のサイズは npm run build 後の .next フォルダ内で確認できます。
Q4. セルフホスティングすると、本当にパフォーマンスが向上しますか?
A: はい、以下の理由でパフォーマンスが向上します:
- First Contentful Paint (FCP) の改善: フォントがビルド時にバンドルされるため、ページロード時の外部リクエストが削減されます
- TTFBの改善: 外部CDNへの接続時間がなくなるため
- キャッシュ効率: ユーザーがサイトを再訪問した際、ブラウザキャッシュからフォントが読み込まれます
ただし、初回ロード時のファイルサイズが増加するため、キャッシュ戦略が重要になります。
Q5. 複数のフォントを同時にセルフホスティングできますか?
A: はい、複数のフォントを同時に使用できます。例えば:
import { Noto_Sans_JP } from 'next/font/google';
import { Noto_Serif_JP } from 'next/font/google';
const notoSansJP = Noto_Sans_JP({ ... });
const notoSerifJP = Noto_Serif_JP({ ... });
// Tailwind CSSでそれぞれのフォントを定義
@theme {
--font-sans: var(--font-noto-sans-jp), sans-serif;
--font-serif: var(--font-noto-serif-jp), serif;
}
Q6. next/fontはNext.js 13以降でも使用できますか?
A: はい、Next.js 13以降(App Router)でも使用できます。設定方法は基本的に同じですが、フォントファイルの最適化がさらに強化されています。
Q7. フォントのライセンスはどうなっていますか?
A: Noto FontsはApache License 2.0で提供されており、商用利用も可能です。詳細はGoogle FontsのNoto Sans JPページで確認してください。
Q8. セルフホスティングのデメリットはありますか?
A: 主なデメリットは以下の通りです:
- ビルドサイズの増加: フォントファイルがバンドルされる
- バージョン管理: フォントの更新時に手動で対応が必要
- CDNとのトレードオフ: キャッシュ戦略を考慮する必要がある
まとめ
Next.jsのnext/fontを使ったNoto Sans JPのセルフホスティングは、シンプルな設定でパフォーマンスとセキュリティの両面で大きなメリットをもたらします。
実装のポイント:
subsets: ['latin']で最小限のプリロード
- CSS変数を使った柔軟なフォント管理
- Tailwind CSS v4の
@themeブロックとの統合
- CSPの簡素化によるセキュリティ向上
得られる効果:
- パフォーマンスの向上(FCP改善、外部通信削減)
- セキュリティの強化(外部依存の排除)
- 開発効率の向上(設定の一元化)
この手法は、日本語フォントを使用するNext.jsプロジェクトであればほぼそのまま適用できる汎用的なソリューションです。プロジェクトの要件に合わせて、ぜひ試してみてください。
参考リンク