😊

Next.js x Three.js での 'window is not defined' を dynamic import で解決する

2022/05/18に公開

経緯

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 に関してはこちらの記事を参考にされたし
https://zenn.dev/ninomium/articles/5fed8af69640a6
公式ドキュメント : Dynamic Import

動的にコンポーネントを読み込むことができる方法である

dynamic import ではオプションで SSR の有無を設定できるものがあり、 dat.GUI を使うコンポーネントは SSR しないという前提で読み込むことでエラーを回避する

  1. window オブジェクトを含むコンポーネントを Canvas.tsx として作成
  2. 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 でやる意義とはなにか?と問いたくなってくるのだがそこはまあいろいろな事情が絡んでくることもあるので、もし同じように困っている人がいたときに少しでも助けになれば良きことかな

参考

https://zenn.dev/ninomium/articles/5fed8af69640a6
https://nextjs.org/docs/advanced-features/dynamic-import
https://qiita.com/ku1987/items/e592cb5133659c3136de
https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97

Discussion