correctness/server-browser-global · サーバー実行モジュールコードでの browser global
モジュールスコープや load・ハンドラで window や document、localStorage を参照すると、SSR が ReferenceError で落ちます。
重大度: critical · カテゴリ: correctness
チェック内容
必ずサーバーで実行されるコードでの browser 専用 global(window、document、localStorage、sessionStorage、navigator、location、history、screen、matchMedia、requestAnimationFrame、cancelAnimationFrame、IntersectionObserver、ResizeObserver、MutationObserver、alert、confirm、prompt)の読み取りを検出します。
.svelte.ts/.svelte.jsrunes モジュールや.svelteの<script module>ブロックのモジュールスコープ(サーバーで import された瞬間にクラッシュ)- SvelteKit のルート/フックファイル:トップレベル、
load/action/エンドポイント handler 本体、initフック(import 時またはリクエストごとにクラッシュ)
検出対象外:
$app/environment(SvelteKit 3 では$app/env)のbrowser(エイリアス込み)や、typeof window !== 'undefined'、typeof matchMedia === 'function'のようなtypeofチェックでガードされたコード(early-return ガードを含む)。onMount/$effect/通常の関数内。モジュール評価時には実行されません。- 裸の
typeof window(throw しない)。 - 自分で import/宣言した名前(
const document = …)。 - handler 内にネストしたクロージャ(典型的にはクライアント側コールバック)。
- 自身がリテラルの
ssr = falseを export するファイル。ルートがサーバーでレンダリングされない universal な+page.ts/+layout.tsのload関数も対象外です(モジュールスコープは引き続き検査します。ページオプションを静的に読めないとき、SvelteKit はそのファイルをサーバーで import するためです)。ページ自身の+pageファイル、続いてレイアウトチェーン(+page@/+layout@によるリセットに従います)の順にたどり、最も近いssrの export がssr = falseなら、そのルートはサーバーでレンダリングされません。svelte.config.jsでkit.router.type: 'hash'を指定したアプリでは、どのルートもサーバーでレンダリングされません。ssr = dev(devは$app/environmentのもの)は、本番ビルドでの値であるfalseとして扱います。ただし、それを export するファイルのモジュールスコープは検査します。SvelteKit がこのオプションを読むためにサーバーで import するからです。それ以外の値で export されたssrは、SSR を再び有効にするものとみなします。同じ階層の universal モジュール(+page.ts/+layout.ts)と server モジュールの両方が export している場合は、SvelteKit と同じく universal 側を採用します。レイアウトは配下のすべてのページがこれに当たる場合に限り、ルートレイアウトは自身がssr = falseを export する場合に限り対象外です。サーバー側のファイル(+page.server.ts、+server.ts、hooks.server.ts)と runes モジュールは引き続き検査します。
なぜ重要か
これらの global は Node に存在しません。モジュールスコープの window 参照はファイルがサーバーで import された瞬間に、load 内なら SSR リクエストのたびにクラッシュします。ReferenceError: window is not defined という、コンパイラが一切警告しない本番 500 です。
修正方法
export function load() {
const stored = localStorage.getItem('filters'); // ❌ サーバーで ReferenceError
return {};
}
ブラウザアクセスをクライアント側、onMount の中へ移します。onMount はサーバーでは実行されません。
<script>
import { onMount } from 'svelte';
let stored = $state(null);
onMount(() => {
stored = localStorage.getItem('filters'); // ✅ onMount はサーバーでは実行されない
});
</script>
または明示的にガードします。
import { browser } from '$app/environment';
const stored = browser ? localStorage.getItem('filters') : null; // ✅
モードによる違い
ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。
無効化
個別に抑制するには、対象行の直前に <!-- svelte-vitals-disable-next-line correctness/server-browser-global --> を置きます。ルールごと無効化するには、次のように設定します。
export default {
rules: {
'correctness/server-browser-global': 'off'
}
};