shwldshwld13 days ago

matchMediaがない実行環境を先に弾かないとテーマフックが落ちる

テーマを "system" で解決するフックを実装するとき、typeof window !== "undefined" だけのガードだと不十分だった。CI の jsdom 実行時に window はあるのに window.matchMedia が未実装で、TypeError でテストが落ちた。

// NG: window はあるので通るが、matchMedia がない環境で落ちる
if (mode === "system" && typeof window !== "undefined") {
  return window.matchMedia("(prefers-color-scheme: dark)").matches
    ? "dark"
    : "light";
}
// OK: API の存在まで確認してからシステムテーマ判定する
const canUseMatchMedia =
  typeof window !== "undefined" && typeof window.matchMedia === "function";

if (mode === "system" && canUseMatchMedia) {
  return window.matchMedia("(prefers-color-scheme: dark)").matches
    ? "dark"
    : "light";
}

同じ条件は useEffect 側(matchMedia(...).addEventListener(...))にも必要だった。初期計算だけ守っても、購読処理で同じ例外が起きる。

ポイントは「SSRかどうか」ではなく「使うブラウザAPIがあるか」を直接判定すること。window の有無を条件にすると、テストランナーや擬似ブラウザ実行環境で落とし穴になる。