window is not defined の直し方
実際に出るエラー
サーバー側で実行されるコードから window や document を参照したときに出るエラーです。
#何が起きているか
window はブラウザにしかありません。サーバー側で実行されるコードから参照すると、未定義の変数を読んだことになって ReferenceError になります。
App Router では、これが2つの場面で起きます。
1. Server Component の中で参照した
→ そもそもサーバーでしか動かないので、必ず落ちる
2. Client Component の中で、描画のときに参照した
→ Client Component も「最初の1回」はサーバーで実行される
2 を見落とすことが多いです。'use client' を書いたから安心、とはなりません。
#Client Component もサーバーで一度動く
名前に引きずられますが、Client Component は「ブラウザだけで動くもの」ではなく「ブラウザにも送られるもの」です。
1. サーバーで実行 ──▶ HTML を作る(ここで window を読むと落ちる)
2. ブラウザへ JavaScript が届く
3. ブラウザで実行 ──▶ 押せるようになる
だから、'use client' の付いたファイルでも、コンポーネントの本体で直接 window を読むと落ちます。
#原因はほぼこの3つ
1. Server Component で参照している
export default function Page() {
const w = window.innerWidth // ← 落ちる
return <div>{w}</div>
}
2. Client Component の本体で参照している
'use client'
export function Widget() {
const saved = localStorage.getItem('key') // ← 最初の1回で落ちる
return <div>{saved}</div>
}
3. import したライブラリが読み込み時に参照している
自分のコードに window が1つも無いのに出る場合はこれです。ライブラリがモジュールの評価時(import した瞬間)にブラウザの API を触っています。
#最短の確認手順
- エラーが出たページのファイルを開く
- 1行目に
'use client'があるか見る - 無ければ → Server Component。ブラウザの API は使えません
- あれば → 参照している場所が「コンポーネントの本体」か「
useEffectの中」かを見る - どちらでもなければ、
importしているライブラリを疑う
#直し方
推奨:useEffect の中で読む
useEffect はブラウザでしか動かないので、ここに置けば安全です。
'use client'
import { useEffect, useState } from 'react'
export function Width() {
const [w, setW] = useState<number | null>(null)
useEffect(() => {
setW(window.innerWidth)
}, [])
return <div>{w ?? '計測中'}</div>
}
最初の描画では null になるので、そのときに何を出すかを決めておいてください。
そもそも CSS で済まないか考える
画面幅による出し分けは、多くの場合メディアクエリで足ります。JavaScript で測ると、最初の描画とその後で表示が変わる(チラつく)ので、CSS のほうが素直です。
ライブラリが原因の場合
読み込み自体を遅らせます。
'use client'
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('some-chart-library'), { ssr: false })
#やってはいけない直し方
const w = typeof window === 'undefined' ? 0 : window.innerWidth
エラーは消えます。ですが、
- サーバー側では必ず
0になる - ブラウザでは実際の値になる
- その差が hydration の不一致として現れる
ので、別の問題に置き換わっただけです。値がサーバーとブラウザで違ってよいのは useEffect の後だけ、と覚えてください。
#なぜ本番だけで出ることがあるのか
そのページが prerender される構成なら、ビルド中に描画が走るのでビルドが失敗して気づけます。
一方、リクエストごとに描画される構成では、そのページを実際に開くまで出ません。開発中に触っていない画面だと、本番で初めて表面化します。
#再発防止
- ブラウザの API を使う場所は
useEffectの中だけ、と決める 'use client'は「ブラウザにも送られる」であって「ブラウザだけ」ではない、と覚える- 新しいライブラリを入れたら、サーバー側で import できるかを最初に確かめる
#関連するエラー
フックを Server Component から import した場合は、描画より前のビルド段階で止まります。
This API is only available in Client Components.
こちらは静的に検出できるぶん、気づくのは早いです。