Next.js x Three.js での 'window is not defined' を dynamic import で解決する
経緯
Three.js では様々なライブラリを import して使うがそもそもがレンダリング後の世界線を前提としているからかライブラリ内で window
オブジェクトを使用していることはままある
そこで問題となってくるのが SSR を前提としている Next.js ではそういったライブラリを import するとエラーとなってしまうことだ
ReferenceError: window is not defined
window
オブジェクトはクライアント ( ブラウザ ) 側でしか動かない。同時にサーバー側でもレンダリングを行う Next.js にとってはこれを処理できない
たとえば Three.js でよく使われるのが dat.GUI というライブラリ ( 説明は省略 ) だが、これは内部で window
オブジェクトを使用しているので普通に読み込むと上記のエラーとなる
今回この dat.GUI の import に際してエラーが出てしまったので解決を試みた
※尚、今回の方法は私がプライベートにいろいろとお勉強をしている環境 ( ≠ プロダクション ) なので実際の運用に関しては未検証でありその点をご留意いただければと思います
'window is not defined' を回避するには ( 一般的な手段 )
基本的にはレンダリング後に window
オブジェクトを使うことは問題ない
なので useEffect()
の中で window
を定義することは可能
...
useEffect(() => {
const windowWidth = window.innerWidth
}, ...)
...
他にも window
が定義されていなければ処理をしないなどといった方法もある
...
if (typeof window === "undefined") {
console.log("Oops, `window` is not defined")
}
...
しかし今回、 dat.GUI では import の時点でエラーになってしまうのでこれらの方法が使えない
dynamic import を使う
dynamic import に関してはこちらの記事を参考にされたしDynamic Import
公式ドキュメント :動的にコンポーネントを読み込むことができる方法である
dynamic import ではオプションで SSR の有無を設定できるものがあり、 dat.GUI を使うコンポーネントは SSR しないという前提で読み込むことでエラーを回避する
-
window
オブジェクトを含むコンポーネントをCanvas.tsx
として作成 -
page
コンポーネントなどでCanvas.tsx
を dynamic import (ssr: false
) する
といった手順でやる
window
オブジェクトを含むコンポーネントを Canvas.tsx
として作成
import { ... } from 'react'
import * as dat from 'dat.gui' // <- この中に window がある
import * as THREE from 'three'
...
const Canvas = (): JSX.Element => {
...
return (
<div id="canvas">...</div>
)
}
export default Canvas
page
コンポーネントなどで Canvas.tsx
を dynamic import する
import { NextPage } from 'next'
import dynamic from 'next/dynamic'
import Header from '@components/Header'
const Canvas = dynamic(() => import('@components/.../Canvas'), {
ssr: false, // <- ここで ssr を無効にするオプションを渡す
})
const ThreeJsPage: NextPage = () => {
return (
<>
<Header />
<main>
<Canvas />
</main>
</>
)
}
export default ThreeJsPage
まとめ
今回は dat.GUI を使うために Canvas
コンポーネントを dynamic import ( ssr:false
) するという方法で window is not found
エラーを回避する方法をここにまとめた
ちなみに Canvas
コンポーネントは、それ全体を ssr: false
として import するのでこの中ではレンダリングのタイミングに関係なく window
オブジェクトを使った処理を書ける
たとえば window.innerWidth
など、 Three.js ではよく使われるのでこれを useEffect()
の外側に定義しておくことも可能。そうすると useEffect()
が無駄に太ることを防ぐこともできる
こうなってくると Next.js でやる意義とはなにか?と問いたくなってくるのだがそこはまあいろいろな事情が絡んでくることもあるので、もし同じように困っている人がいたときに少しでも助けになれば良きことかな
参考
Discussion