既存プロジェクトとデザインシステムを統一するため、内部開発のアプリのフォントを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)の最適化

実装方針

  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の設定

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 との統合がシームレスで、デザインシステムの保守性が向上しました

トラブルシューティング

フォントが正しく読み込まれない

現象: ビルド後にフォントが適用されず、フォールバックフォントが表示される

原因と解決策:

  1. subsetsの指定ミス

    // ❌ 誤った指定
    subsets: ['japanese']  // この指定は不要
    
    // ✅ 正しい指定
    subsets: ['latin']  // 日本語は自動で含まれる
    
  2. 環境変数の未設定

    # 環境変数が正しく設定されているか確認
    echo $NODE_ENV  # production であることを確認
    
    # 開発環境ではnext devで確認
    npm run dev
    
  3. キャッシュの問題

    # Next.jsキャッシュをクリア
    rm -rf .next
    npm run build
    

CSS変数が適用されない

現象: Tailwind CSSのfont-sansが適用されない

解決策:

  1. html要素のクラス確認

    <!-- html要素にfontクラスが適用されているか確認 -->
    <html lang="ja" class="font-sans">
    
  2. globals.cssの@themeブロック確認

    @theme {
      --font-sans: var(--font-noto-sans-jp), -apple-system, BlinkMacSystemFont,
        'Segoe UI', Roboto, sans-serif;
    }
    

ビルドサイズが大きくなる

現象: フォントファイルのためにビルドサイズが増加する

解決策:

  1. 必要なweightのみを指定

    const notoSansJP = Noto_Sans_JP({
      weight: ['400', '500', '700'],  // 実際に使用するweightのみ
      // ...
    });
    
  2. preloadの無効化(必要に応じて)

    const notoSansJP = Noto_Sans_JP({
      display: 'swap',
      variable: '--font-noto-sans-jp',
      preload: false,  // 明示的にpreloadを無効化
    });
    

FOUT(Flash of Unstyled Text)が発生する

現象: フォント読み込み中に一時的にフォールバックフォントが表示される

解決策:

  1. displayオプションの確認

    const notoSansJP = Noto_Sans_JP({
      display: 'swap',  // FOUTを許容して読み込み速度を優先
      // display: 'block' // 完全な読み込みを待つ場合(遅延が発生)
    });
    
  2. 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のセルフホスティングは、シンプルな設定でパフォーマンスとセキュリティの両面で大きなメリットをもたらします。

実装のポイント:

  1. subsets: ['latin']で最小限のプリロード
  2. CSS変数を使った柔軟なフォント管理
  3. Tailwind CSS v4の@themeブロックとの統合
  4. CSPの簡素化によるセキュリティ向上

得られる効果:

  • パフォーマンスの向上(FCP改善、外部通信削減)
  • セキュリティの強化(外部依存の排除)
  • 開発効率の向上(設定の一元化)

この手法は、日本語フォントを使用するNext.jsプロジェクトであればほぼそのまま適用できる汎用的なソリューションです。プロジェクトの要件に合わせて、ぜひ試してみてください。

参考リンク