コンテンツにスキップ

CORRECT008 · サーバー実行モジュールコードでの browser global

重大度: critical · カテゴリ: correctness

必ずサーバーで実行されるコードでの browser 専用 global(windowdocumentlocalStoragesessionStoragenavigatorlocationhistoryscreenmatchMediarequestAnimationFramecancelAnimationFrameIntersectionObserverResizeObserverMutationObserveralertconfirmprompt)の読み取りを検出します:

  • .svelte.ts/.svelte.js runes モジュールや .svelte<script module> ブロックのモジュールスコープ(サーバーで import された瞬間にクラッシュ)
  • SvelteKit のルート/フックファイル — トップレベル、load/action/エンドポイント handler 本体、init フック(import 時またはリクエストごとにクラッシュ)

検出対象外: $app/environmentbrowser(エイリアス込み)や typeof window !== 'undefined' チェックでガードされたコード(early-return ガードを含む)、onMount/$effect/通常の関数内(モジュール評価時には実行されない)、裸の typeof window(throw しない)、自分で import/宣言した名前(const document = …)、handler 内にネストしたクロージャ(典型的にはクライアント側コールバック)、自身が ssr = false を export するファイル。

これらの global は Node に存在しません。モジュールスコープの window 参照はファイルがサーバーで import された瞬間に、load 内なら SSR リクエストのたびにクラッシュします — ReferenceError: window is not defined、コンパイラは一切警告しない本番 500 です。

+page.ts
export function load() {
const stored = localStorage.getItem('filters'); // ❌ サーバーで ReferenceError
return {};
}

ブラウザアクセスをクライアント側へ移します:

+page.svelte
<script>
let stored = $state(null);
$effect(() => {
stored = localStorage.getItem('filters'); // ✅ effect はサーバーでは実行されない
});
</script>

または明示的にガードします:

import { browser } from '$app/environment';
const stored = browser ? localStorage.getItem('filters') : null; // ✅