📝

SvelteKit + Cloudflare Pages で一部の Safari だけ真っ白になった現象の対応・調査メモ

に公開

背景

SvelteKit(Svelte 5)+ Cloudflare Pages (Free Plan) を使用した Web アプリにて、一部の iOS Safari でのみ画面が真っ白になるという現象が発生しました。Windows、Android、macOS Chrome では問題なく動作しており、特定の Safari 環境でのみ再現する不具合です。

発生アプリ: lumilumi.app(機能追加作業中に問題が顕在化)

真っ白の原因を探る

モバイル端末や Safari の開発者ツールが直接使えない環境でもエラーを特定するため、Web Inspector(iOSアプリ) や、ブラウザ内で動作する DevTools ライブラリ eruda を使用し、iPhone Safari 上で JavaScript エラーの可視化と検証を行いました。

  • Web Inspector:任意のウェブサイトに対して、JavaScript のエラーやログを iOS 上で確認できるアプリ
  • eruda:スクリプトを埋め込むことで、ブラウザ上で DevTools ライクなインターフェースが使えるライブラリ

実際に表示されたエラーは次の通り:

SyntaxError: Invalid character '\uFFFD'

このエラーは、ブラウザが受信したスクリプト内のバイト列を正しくデコードできず、**Unicode の「置換文字(\uFFFD)」**に置き換えた際に、構文解析(パース)に失敗したことを示しています。

原因

Cloudflare Pages によって配信された Brotli 圧縮ファイルが、一部の CDN ノードで破損した状態でキャッシュ・配信されていた可能性が高いです。

  • Cloudflare は .js.css などの静的ファイルを自動的に Brotli(.br)圧縮
  • エッジキャッシュに破損状態で保存されることがある
  • iOS Safari がそのファイルを展開しようとした際に UTF-8 デコードに失敗し、\uFFFD を含む状態で JavaScript のパースに失敗
  • SyntaxError: Invalid character '\uFFFD' → JavaScript 実行停止 → 画面が真っ白に

この問題は iOS Safari(特に古いバージョン)で発生しやすいとの報告もあります。

.pages.devでは正常、.appでエラー

以下のように、同じコードベースでもドメインによって挙動が異なっていました:

ドメイン 状態 備考
https://lumilumi.pages.dev ✅ 正常表示 デフォルトの Cloudflare Pages ドメイン
https://lumilumi.app ❌ 一部 Safari で真っ白 独自ドメイン(Cloudflare Proxy 有効)

これは Cloudflare のエッジキャッシュがドメインごとに分かれているため、.pages.dev.app では異なる CDN ノード/キャッシュ状態になっていたと推定されます。

解決法:Cloudflare の Brotli 圧縮を無効化する

根本的な回避策として、Cloudflare 上で Brotli を無効化し、Gzip のみに制限することが推奨されます。

手順

  1. Cloudflare Dashboard → 対象のドメインを選択
  2. Rules → Compression Rules を開く
  3. Disable Brotli Compression [Template]」を選択
  4. 「Then Compression options」→ Custom を選択
  5. 「Define a custom order for compression types」→ Gzip のみを追加
  6. Brotli や Zstd を削除(Zstd は Enterprise 専用のため表示されない場合あり)
  7. 保存して設定反映を待つ

設定結果例

圧縮形式 状態
Gzip ✅ 有効
Brotli ❌ 無効
Zstd ❌ 無効(対象外)

設定確認

curl -s -I https://yourdomain.com/assets/index-*.js | grep -i content-encoding
# Content-Encoding: gzip が表示されれば OK
# br(Brotli)が含まれていたら未設定の可能性

✅ CSS も破損していた(追加対応)

今回のケースでは .js に加えて .csscontent-encoding: zstd で配信されており、Safari が非対応のため展開に失敗 → スタイルが反映されないという二次不具合も確認されました。

対処方法

  1. Cloudflare Dashboard → Rules → Response Header Transform
  2. content-encodingSet Static にし、値に gzip を指定
  3. 変更後、Cloudflare の「Purge Everything」でキャッシュを全削除
  4. Safari 側でもリロード or キャッシュクリアを実施

なぜ Safari だけ影響を受けるのか

  1. UTF-8 構文に対する厳格さ

    • 他のブラウザはパース時に多少のデコードミスを許容するが、iOS Safari は失敗時に実行を中断する傾向が強い
  2. 破損キャッシュの受信状況が環境依存

    • 特定の CDN ノードだけに破損キャッシュが存在
    • iOS Safari のキャッシュが切れたタイミングで、それを取得してしまった
  3. zstd に未対応の環境がある

    • .csszstd 圧縮されていると Safari では読み込みエラーになるケースがある

なぜ突然発生するようになったのか

Brotli 圧縮は以前から使われていたにも関わらず、2025年現在でこの問題が顕在化した要因には以下のような可能性があります:

  • Cloudflare 側のキャッシュ仕様変更
  • エッジノードでのキャッシュ破損の頻度増加
  • ファイルサイズ増加に伴う圧縮強度の変化

まとめ

内容 説明
原因 Cloudflare Pages で配信される Brotli 圧縮ファイルが、一部の CDN ノードで破損し、Safari がパース不能になる
現象 SyntaxError: Invalid character '\uFFFD' が発生し、JavaScript 実行停止 → ページが真っ白になる
対策 Cloudflare Compression Rules にて Brotli を無効化、Gzip のみに制限
補足 .pages.dev では正常でも、.app では破損キャッシュが原因で発生する例がある
CSS .csszstd 圧縮された場合も Safari でスタイルが崩れるため、 gzip に統一 + キャッシュ削除が必要
確認方法 curl -IContent-Encoding: gzip を確認(brzstd が含まれないこと)

今後の対策

  • Cloudflare の Brotli 圧縮は無効化を継続
  • デプロイ後は curl や Chrome DevTools で配信ヘッダーを確認
  • Safari のキャッシュを個別に削除 or プライベートモードで確認
  • モバイル Safari のエラー検出用に eruda を一時的に導入しておくと便利

関連リンク

Discussion