本文へ移動
verified onnext@16.3.3react@19
最終検証日 2026-08-27
Next.js道場

window is not defined の直し方

実際に出るエラー

ReferenceError: window is not defined
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /page: /, exiting the build.

サーバー側で実行されるコードから 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. エラーが出たページのファイルを開く
  2. 1行目に 'use client' があるか見る
  3. 無ければ → Server Component。ブラウザの API は使えません
  4. あれば → 参照している場所が「コンポーネントの本体」か「useEffect の中」かを見る
  5. どちらでもなければ、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.

こちらは静的に検出できるぶん、気づくのは早いです。

理解できたか試す

では、このコードはどうなりますか。

このページには use client がありません。どうなりますか。

この問題を解くこのテーマの問題 3