🍺

CloudFront + S3でVuetifyアプリをデプロイしたらリロード時に503エラーが大量発生した話

に公開

概要

Vuetify 3で構築したSPAをCloudFront + S3にデプロイしたところ、最初の表示は正常だが、ブラウザをリロードすると大量の503エラーが発生し、画面が表示できなくなる問題が発生しました。

環境

  • Vite: 7.1.5
  • Vuetify: 3.10.1
  • unplugin-fonts: 1.4.0
  • デプロイ先:Amazon CloudFront + S3

先に結論

  • 原因: Vuetify公式テンプレートのフォント設定をデフォルトのまま使い、Robotoの全ウェイト・全言語バリアントをpreloadしてしまったことで200件超のフォントリクエストが一斉発生。
  • 影響: ブラウザの同時接続数制限でフォント取得が滞留し、タイムアウトして503エラーが多発。
  • 対応: vite.config.mts のFonts設定で必要なウェイトのみ(例: 400/500/700)、スタイルはnormalのみにし、subset: 'latin' を指定してフォント数を絞る。
  • 効果: preload数が10件に減り、リロード時も503エラーが解消。

症状

初回アクセス時

  • ✅ 正常に表示される
  • ✅ SPA内でのページ遷移も問題なし

リロード時

  • ❌ 大量の503エラー(Service Unavailable)
  • ❌ フォントファイルの読み込みに失敗
  • ❌ 画面が正しく表示されない

エラーの詳細

https://***/assets/roboto-vietnamese-500-normal-CUzdRVXb.woff 
net::ERR_ABORTED 503 (Service Unavailable)

DevToolsのNetworkタブで確認すると:

  • 最初の数十個のフォントファイルは 200 OK で取得成功
  • 徐々に503エラーになっていく
  • 合計200個以上のフォントファイルがリクエストされている

原因の特定プロセス

最初の仮説:CloudFront/S3の設定ミス

以下を確認:

  • ✅ S3にファイルは存在している
  • ✅ CloudFrontのOrigin Access Control (OAC) が設定されている
  • ✅ S3のバケットポリシーも正しく設定されている
  • ✅ Error Pages設定(404/403 → index.html)も設定済み

→ 設定は問題なし

次の仮説:レート制限?

S3/CloudFrontのレート制限に引っかかっているのでは?

  • S3の制限:5,500リクエスト/秒/prefix
  • CloudFrontの制限:ほぼ無制限

→ 200個程度のリクエストでレート制限に引っかかることはない

真の原因を発見

DevToolsで詳しく観察すると:

  1. 200個以上のフォントファイルがpreloadされている
  2. リロード時に一斉にリクエストが発生
  3. ブラウザの**同時接続数制限(6〜8接続)**により、後半のリクエストは長時間キューで待機
  4. **キューイング中にタイムアウト(30秒)**を超えてしまう
  5. 結果として503エラーになる
リクエストの流れ:
Font 1-6   → 200 OK(すぐ取得)
Font 7-12  → 200 OK(少し待機)
Font 13-18 → 200 OK(さらに待機)
...
Font 100+  → 503 Service Unavailable(タイムアウト)

根本原因:Vuetifyのデフォルトフォント設定

vite.config.mts のFontsプラグイン設定:

Fonts({
  fontsource: {
    families: [
      {
        name: 'Roboto',
        weights: [100, 300, 400, 500, 700, 900], // 6ウェイト
        styles: ['normal', 'italic'],             // 2スタイル
        // subset指定なし!
      },
    ],
  },
}),

なぜ200個以上になるのか

6ウェイト × 2スタイル = 12バリエーション

言語バリアント(subset未指定のデフォルト):
- latin
- latin-ext
- cyrillic
- cyrillic-ext
- greek
- vietnamese
- symbols
- math

12バリエーション × 9言語 × 複数フォーマット(woff2, woff) 
= 200個以上のファイル

Vuetifyの公式テンプレートの問題点

Vuetifyの公式テンプレートは「あらゆる用途に対応できるように」という配慮で、すべてのフォントウェイトと言語バリアントを含む設定になっています。

しかし、本番環境にそのままデプロイすると今回のような問題が発生します。

解決方法

フォント設定の最適化

vite.config.mts を以下のように修正:

Fonts({
  fontsource: {
    families: [
      {
        name: 'Roboto',
        weights: [400, 500, 700], // 必要なウェイトのみ
        styles: ['normal'],        // イタリック不要
        subset: 'latin',           // 英数字のみ(重要!)
      },
    ],
  },
}),

変更のポイント

  1. ウェイトを絞る:100, 300, 900は通常使わない
  2. イタリックを削除:必要なければnormalのみ
  3. subsetを指定:これが最も重要!日本語サイトならlatinのみで十分

結果

最適化前

  • ❌ 200個以上のpreloadタグ
  • ❌ 194個のRobotoフォントファイル
  • ❌ リロード時に503エラー多発

最適化後

  • ✅ 10個のpreloadタグ(Roboto 6個 + Material Design Icons 4個)
  • ✅ 6個のRobotoフォントファイル(latin-400/500/700のwoff2とwoff)
  • ✅ リロード時も503エラーなし!

学び

1. 公式テンプレートのデフォルト設定をそのまま使わない

多くのフレームワークの公式テンプレートは「開発をすぐに始められる」ことを優先しており、本番環境での最適化は開発者に委ねられています。

2. フォントの最適化は必須

一般的なWebアプリで必要なフォントウェイトは3〜4種類程度:

  • 400(Regular):通常のテキスト
  • 500(Medium):少し強調したいテキスト
  • 700(Bold):見出しや強調

100(Thin)や900(Black)はデザイン上の特別な用途以外では不要です。

3. subsetの指定を忘れずに

subset: 'latin' を指定しないと、全言語バリアント(Cyrillic、Greek、Vietnameseなど)がダウンロードされます。日本語サイトであれば、英数字用のlatinのみで十分です。

最後に

「サクッとCloudFront + S3にSPAをデプロイしよ!」と深夜に酒を飲みながら始めました。
アプリはログイン画面ともう1画面を作成してすぐ終わり、AWS側の設定もすぐ終わりました。
(今回はCDKなどで構築せず、S3にポイッとdistの中身を格納しただけなので...)

フォント周りの設定やブラウザ制限などの普段特段意識していない分野でのエラーだったので、勉強になりました。

ミニマム構成が正義ですなぁ...

Discussion