本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

window・document 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、localStorage を参照したときに出るエラーです。

何が起きているか

window はブラウザにしかありません。サーバー側で実行されるコードから参照すると、未定義の変数を読んだことになって ReferenceError になります。

App Router では、これが2つの場面で起きます。

どこで参照したか。原因はこの2つに分かれる
参照した場所落ちるかなぜ
Server Component の中できないそもそもサーバーでしか動かないので、必ず落ちる
Client Component の描画の中できない初回表示のために一度サーバーで実行されるため
useEffect の中できるブラウザでしか動かない。ここが正しい置き場所

2 を見落とすことが多いです。'use client' を書いたから安心、とはなりません。

Client Component もサーバーで一度動く

use client を書いても、サーバーでの実行は無くならない
  1. Server
    モジュールが読み込まれる

    ファイルの先頭に書いた処理はここで動く。window はまだ無い。

  2. Server
    初回表示の HTML を描く

    描画の途中で window を読むと、ここで落ちる。

  3. Browser
    hydration のあとに useEffect

    window / localStorage を読んでよいのはここだけ。

名前に引きずられますが、Client Component は「ブラウザだけで動くもの」ではなく「ブラウザにも送られるもの」です。

だから、'use client' の付いたファイルでも、コンポーネントの本体で直接 window を読むと落ちます。

document も localStorage も同じ

ブラウザにしかないものは、window だけではありません。Client Component の本体で読んで、実機で確かめました。

Client Component の本体で読んだとき(実機で確かめた)
読んだものビルドで出た文言
window.innerWidthReferenceError: window is not defined
document.titleReferenceError: document is not defined
localStorage.getItem('k')ReferenceError: localStorage is not defined

どれも、事前に描く段階(Error occurred prerendering page)で止まりました。原因も直し方も、このページの 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 しているライブラリを疑う

直し方

読む場所を変える。条件で囲んでも直らない
うまくいかない
'use client'
 
export function Box() {
  const w = window.innerWidth
  return <div>{w}</div>
}

描画の途中で読んでいる。サーバーで落ちる。

こう書く
'use client'
 
export function Box() {
  const [w, setW] = useState(0)
  useEffect(() => setW(window.innerWidth), [])
  return <div>{w}</div>
}

最初は既定値で描き、ブラウザに着いてから差し替える。

推奨: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.

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

useEffect に移さずに、サーバーとブラウザで違う値を描いてしまうと、今度は hydration の食い違いになります(Hydration failed の直し方)。

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

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

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

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