🏃‍♀️

Phaser.jsをNext.js上で動かす方法

2024/02/02に公開

Phaser.jsのようなツールはSSR上で利用することができないため、Next.js上で普通にimportして使うとエラーが出てしまいます。この記事ではそのような理由で、詰まってしまった人のために、解決策をいくつか例示しようと思います。

まずは普通に書く

最初は普通に書いてみましょう。
Phaserのインストール

npm install phaser
index.tsx
import Phaser from "phaser";
import { useEffect, useRef } from "react";

export default function Home() {
  const gameArea = useRef(null);

  useEffect(() => {
    if (!gameArea.current) return;
    const game = new Phaser.Game({
      width: 800,
      height: 600,
      parent: gameArea.current,
    });
  }, []);

  return <div ref={gameArea}></div>;
}

Next.jsの設定にもよりますが、このようなエラーが出ます。

ReferenceError: HTMLVideoElement is not defined

Node.js上(サーバー上)では HTMLVideoElementは定義されていないのでこのようなエラーが出てしまいます。以下はその解決策の例です。

useEffectとimport文を使用したパターン

useEffectを使用すると、サーバーサイドではなく、クライアントサイドでコードを実行することができます。ここでPhaser.jsを呼び出して使うことで、サーバー上で動かしたことに起因するエラーを回避することができます。

index.tsx
import { useEffect, useRef } from "react";

export default function Home() {
  const gameArea = useRef(null);

  useEffect(() => {
    const makeGame = async () => {
      if (!gameArea.current) return;
      //Phaser.jsをここでインポートする
      const phaser = await import("phaser");
      //ゲームを作成
      const game = new phaser.Game({
        title: "テスト",
        width: 800,
        height: 600,
        parent: gameArea.current,
      });
    };
    makeGame();
  }, []);

  return <div ref={gameArea}></div>;
}

このように書くとPhaser.jsがクライアントサイドで読み込まれ実行されるので、エラーを回避することができます。

dynamicImportを使用したパターン

Next.jsのdynamicImportを使用すると、第二引数で ssr:false を設定することにより、クライアントサイドでインポートすることができます。

https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic

index.tsx
import dynamic from "next/dynamic";

export default function Home() {
  //dynamicImportを用いて、game.tsxを読み込む
  const Game = dynamic(import("./game"), {
    //これで、ssrが実行されない
    ssr: false,
    //読み込んでいる途中に表示されるコンポーネント
    loading: () => <p>読み込み中...</p>,
  });

  return <Game></Game>;
}
game.tsxのコード
game.tsx
//普通に書く
import Phaser from "phaser";
import { useEffect, useRef } from "react";

export default function Home() {
  const gameArea = useRef(null);

  useEffect(() => {
    if (!gameArea.current) return;
    const game = new Phaser.Game({
      width: 800,
      height: 600,
      parent: gameArea.current,
    });
  }, []);

  return <div ref={gameArea}></div>;
}

これで、エラーを回避することができ、読み込み中は「読み込み中...」と画面に表示されるようになります。

最後に

最後に宣伝みたいになりますが(実際そう)、Next.jsとPhaser.jsを使用して個人でゲームサイトを作成したので、よかったら見ていってください。
https://kyouryoku-play.vercel.app/
あと、できるだけ間違いがないようにしていますが、万一間違いがあった場合はコメントで指摘してくださると幸いです。

Discussion