本日はNext.jsでウィンドウオブジェクトにアクセスするいくつかの方法を初心者向けに解説していきます。
Next.jsを使用するときに遭遇する可能性のある非常に一般的なエラーは、ウィンドウオブジェクトにアクセスした際に返されるエラーです。
Next.jsではWindowは定義されておりません。
Next.jsアプリで HTML DOMからウィンドウオブジェクトにアクセスしようとすると以下のエラーがスローされます。
Unhandled Rejection (ReferenceError): window is not defined
「Windowが定義されていません」と返ってきます。
ターミナル自体にエラーが発生します、つまりコンパイルエラーとなります。
window is not defined
このエラーは、サーバー側のコードでウィンドウオブジェクトにアクセスしようとすると発生します。
このエラーは通常、Next.jsおよびNode.jsのアプリケーションで発生しNode.jsサーバーがブラウザ環境を提供していないためとなります。
つまり、Next.jsでは、ウィンドウオブジェクトがクライアント側でのみ利用可能であという制限があります。
NextJSでWindowが定義されていないのはなぜですか?
Next.jsのドキュメントによると、以下のように記載されております。
Next.jsは、本番環境に必要なすべての機能 (ハイブリッド静的レンダリングとサーバー レンダリング、TypeScript サポート、スマートバンドル、ルートプリフェッチなど) を備えた最高の開発者エクスペリエンスを提供します。
ウィンドウオブジェクトは、ブラウザで開いているウィンドウへの参照です。
したがって、ウィンドウオブジェクトはクライアント側でのみ使用できます。
Next.jsはNode.jsのランタイムでコンパイル・ビルドされており、Node.jsはウィンドウオブジェクトを持ちません。
では、なぜNode.jsで動作させるのか?
Next.jsは、Node.js上で動作するように設計されています。
Node.jsは、JavaScriptをサーバーサイドで実行するためのプラットフォームであり、高速でスケーラブルなWebアプリケーションを構築するために広く使用されています。
Next.jsは、Node.jsの機能を活用して、サーバーサイドでReactコンポーネントをレンダリングし、HTMLを生成することができます。
また、Node.jsのパッケージマネージャーであるnpmを使用して、依存関係の管理やビルドプロセスの自動化を行うことができます。
これにより、開発者は簡単にNext.jsアプリケーションを構築し、デプロイすることができます。
Next.jsがサーバーで実行されるウィンドウオブジェクトがページに含まれている場合、その問題が発生する可能性があり、その環境では、window
にアクセスする事は不可能となります。
総じて、Next.jsは、SSRをサポートするために、ウィンドウオブジェクトを制限しています。
これは、Reactのコンポーネントがサーバー側でレンダリングされるため、ブラウザのウィンドウオブジェクトにアクセスできないためです。
Next.jsで"window is not defined"を回避するには?
このエラーを回避する方法がいくつかございます。
まず、先述で解説した通り、Next.jsでは、サーバーサイドレンダリング(SSR)を有効にすると、コンポントがサーバー側でレンダリングされるため、ブラウザのウィンドウオブジェクトにアクセスできせん。
そのため、ウィンドウオブジェクトにアクセスする必要がある場合は、「SSR」
を無効にする必要があります。
Next.jsアプリケーションでウィンドウオブジェクトにアクセスするクールな方法をいくつか紹介しましょう。
・ ウィンドウ オブジェクトが定義されているかどうかを型で確認する
・ React useEffectフック内でwindowオブジェクトを使用するか、またクラスコンポーネントではcomponentDidMountライフサイクルメソッドを使用します。
・ globalThisプロパティを使用してウィンドウにアクセスする
・ Next.jsが提供する組み込み関数を使用する
useEffectフックを使用する
useEffect内のウィンドウオブジェクトをいじっても問題はありませんので以下のメソッド内のコードで常に使用できます。
const [width, setWidth] = useState(0); useEffect(() => { setWidth(window.innerWidth); },[]);
なぜ、useEffectフックでは使用できるのか?
それは useEffectフックは、サーバーサイドでは実行されないためです。
ReactのuseEffect フックを使用すると、初回レンダリング後にuseEffectが実行されるので、コードはアプリケーションのクライアントサイドでのみ実行されます。
もちろん上記のように、useEffect単独で使用しても構いませんがNext.js外部のブラウザ(DOMノードなど)でオブジェクトを取得し、その中に何かをレンダリングする必要がある場合はuseEffect
およびuseRef
フックと組み合わせることもできます。
import { useRef, useEffect } from 'react' const Page = () = > { const ref = useRef() useEffect(() => { ReactDOM.render(<Test /> ref.current); }, []) return <div ref={ref}/> }
Dynamic import(動的インポート)
コンポーネント内の複数の場所で、ウィンドウオブジェクトにアクセスしていて、以下のメソッドを常に使用することはあまり現実的ではないと仮定します。
const Home = () => { useEffect(() => { window.Message = "Welcome!" }, []) return <div>Hello</div> }
コンポーネント全体をクライアント側でのみ実行したい場合は 、Next.jsの動的インポート機能を使用できます。
Next.jsにはDynamic import
があります。
ブラウザでのみ動作するライブラリを含むモジュールです。
import dynamic from "next/dynamic" const TestComponent = dynamic(() => import("../components/TestComponent"), { //サーバーサイド側でインポートはしない ssr: false, // SSRの無効 }) const Home = () => { useEffect(() => { window.Message = "Welcome!" }, []) return <div> <TestComponent /> </div> }
上記のように、TestComponent
を動的にインポートすることができるようになり、コードを実行してもエラーがスローされる事はありません。
Dynamic import
を使用することをお勧め致します。
型のチェック
Reactの外部でも役立つ別のオプションがあります。
現在のコンテキストに、ウィンドウオブジェクトが定義されているかどうかをチェックするだけで、コンポーネントのSSRを無効にする方法となります。
const [width, setWidth] = useState(0); if( typeof window !== undefined) { setWidth(window.innerWidth) } // または if( typeof window === "object") { setWidth(window.innerWidth) }
このアプローチは最も単純で簡単となります。
必要なコードを実行する前に、ウィンドウオブジェクトの型を確認するだけです。
上記のようにすれば、Windowグローバル変数へのアクセスを提供します。
この方法で問題は解決します、ブラウザ環境では条件内でしか実行しないからです。
globalThisプロパティを使用する
globalThisプロパティは、環境全体でグローバルとしてthis
参照へのアクセスを提供してくれます。
グローバルオブジェクトは、コード内のどこからでもアクセスできるJavaScriptの特別なオブジェクトであり、ブラウザではウィンドウオブジェクト、Node.js環境ではglobalオブジェクトとして知られています。
const Home = ({data}) => { console.log(globalThis.window?.innerWidth); return <>{data.post.text}</> }
これはthis
に似たグローバル値となっています。
this
などの同様のプロパティとは異なり、ウィンドウ コンテキストと非ウィンドウコンテキストで動作することが保証されております。
globalThisプロパティは、ES2020で導入され、ES2021以降のすべての環境で利用可能となっております。
Next.jsでは、SSRを無効にする方法として、getInitialProps
メソッドを使用することができます。
組み込み関数でウィンドウオブジェクトにアクセスする
getInitialProps
メソッドは、コンポーネントがレンダリングされる前に実行され、クライアント側でのみ実行されます。
このため、getInitialProps
メソッド内でウィンドウオブジェクトにアクセスすることができます。
しかし、getInitialProps
メソッドは、「Next.js v9.3」以降では非推奨となっており、代わりにgetStaticProps
またはgetServerSideProps
を使用することが推奨されています。
getStaticProps
またはgetServerSideProps
を使用して、SSRを無効にし、ウィンドウオブジェクトにアクセスする例を以下に示します。
import React from 'react'; // Reactコンポーネント const MyComponent = ({ windowWidth }) => { return ( <div> <p>Window width: {windowWidth}px</p> </div> ); }; // サーバーサイドで実行される関数 export async function getServerSideProps() { // サーバーサイドで実行されているかどうかを確認 const isServer = typeof window === 'undefined'; let windowWidth = 0; if (!isServer) { // クライアント側で実行されている場合は、ウィンドウ幅を取得 windowWidth = window.innerWidth; } // propsとしてウィンドウ幅を返す return { props: { windowWidth, }, }; } export default MyComponent;
上記の例では、getServerSideProps
メソッド内で、typeof window === 'undefined'
を使用して、サーバーサイドで実行されているかどうかを確認しています。
サーバーサイド実行されている場合は、window.innerWidth
にアクセスできないため、windowWidth
を0
に設定します。
クライアント側で実行されている場合は、window.innerWidth
にアクセスして、windowWidth
に値を設定します。
getStaticProps
メソッドを使用する場合も同様に、typeof window === 'undefined'
を使用して、サーバーサイドで実行されているかどうかを確認し、ウィンドウオブジェクトにアクセスします。
注意点として、getStaticProps
またはgetServerSideProps
メソッドは、「Next.js v9.3」以降でのみ使用できます。
また、getStaticProps
メソッドは、ビルド時に実行されるため、データが頻繁に変更される場合は使用しない方が良いです。
一方、getServerSideProps
メソッドは、リクエストごとに実行されるため、データが頻繁に変更される場合でも使用することができます。
最後に
Next.jsでウィンドウオブジェクトにアクセスする方法は、Reactアプリケーションの開発において非常に重要です。
ウィンドウオブジェクトにアクセスすることで、ブラウザのウィンドウサイズやスクロール位置などの情報を取得することができます。
また、window.addEventListener
を使用して、ウィンドウのリサイズやスクロールなどのイベントを監視することができます。
これにより、ウィンドウの変更に応じてコンポーネントを再レンダリングすることができます。
ですが、初めてNext.jsを使用する際に遭遇する可能性のあるエラーである"ReferenceError: window is not defined"
について心配する必要はありません。
Next.jsでは、コードがクライアントとサーバーの両方で実行されることを念頭に置く必要があります。
この記事では、Next.jsでウィンドウオブジェクトにアクセスするためのいくつかの方法を初心者向けに紹介しました。
プロジェクトの要件に基づいて、どの方法が最適かを決定する必要があります。
また、SSRを無効にするかどうかについても、慎重に考慮する必要があります。
本日は以上となります。
最後までこの記事を読んで頂きありがとうございます。
この記事が役に立ったら、ブックマークと共有していただけると幸いです。