チャットApp(next.jsでfirebaseを使う時の注意点)

今日は、firebaseに登録したユーザ情報の取得で苦戦したので、それを書き残しておこうと思います。

next.jsにはダイナミックルーターという動的にURLを指定できる機能がある。
例えば今回のプロフィールページに関して言えばpagesディレクトリ内にprofile/[username].tsxとファイルを作成してあげると、[username]の部分をユーザ名で指定してルーティングしてあげることができる。
/profile/user1と指定すると[username].tsxがあたかもuser1のためだけに作ったhtmlファイルのように振舞う。propsを上手く渡してあげれば、user1固有のデータを取ってきて表示することもできる。

今回はfirebaseを使用しているのでfirebaseのdisplayNameでルーティングしてあげれば楽勝と思っていた。いや、ルーティングだけなら楽勝だった。

/profile/[username].tsx

import { useState, useEffect } from "react";
import BasicHead from "../../components/atom/head";
import TitleLogo from "../../components/atom/logo";
import BasicParagraph from "../../components/atom/basicP";
import Styles from "../../styles/profile.module.css";
import { getActiveUser } from "../../functions/auth";
export default function Profile() {
   const [user, setUser] = useState<firebase.User>();
   useEffect(() => {
       const temp = getActiveUser();
       setUser(temp);
       console.log(temp);
   }, [user]);

   return (
       <div>
           <BasicHead />
           <main>
               <div className={ Styles.title }>
                   <TitleLogo />
               </div>
               <BasicParagraph>
                   { user?.displayName || "no user" }
               </BasicParagraph>
               <img src={ user?.photoURL } />
           </main>
       </div>
   );
}

このようにすれば、一見動きそうなもんだが動かない。動かないというかno userが表示されたままで一向に本来のユーザ名が表示されない。

ページ遷移毎にfirebaseの初期化が起こっている。

どうやら悪さをしているのは、ページ遷移ごとに起きるfirebaseの初期化が原因のようだ。これは、公式のドキュメントに書いてあった訳ではなく私の推測なのだが、firebaseは最初に初期化したオブジェクトでユーザのログイン状態やfirestoreなどとの通信を行っているようだ。私は、各ページで関数化したfirebaseの機能を読み込んでおり、ページを読み込む毎firebaseの初期化が実行されているので、ユーザのログイン状態が失われ、ログインしても遷移先(今回だとプロフィールページ)でユーザ情報が取得出来ない状態になっていたようだ。

解決策としては、_app.jsでfirebaseオブジェクトを読み込むことだ。_aspp.jsはnext.jsが用意してるものでcreate-next-appでプロジェクトを作成したらpagesディレクトリに用意されているものだ。全てのページはこの_app.jsを通して表示される。逆に言えばapp以外のページに移動しない限り、_app.jsに書かれた状態は保ち続けられるのでここでfirebaseオブジェクトを読み込んで初期化しておけば、他のページに移動してfirebaseの機能を読み込み使用しても初期化は起こらないということだ。

app内のいろいろなページでfirebaseの機能を利用するときは、_app.jsで初期化しよう。

_app.js

import '../styles/globals.css'
import FB from "../functions/firebase";
function MyApp({ Component, pageProps }) {
 (async() => {
   await FB.onAuthStateChanged(user => {
    // 初めに読み込んだページで初期化される。
   });
 })();
 return <Component {...pageProps} />
}
export default MyApp


ルーティング

next.jsにはクライエントサイドナビゲーションと言ってJSが必要な部分だけを読み込んで表示します。以下のページではデベロッパーツールをしようして背景を黄色にした後ページ遷移をしてますが、背景の黄色が変わっていないところからJSで必要な部分だけが読み込まれていることが確認出来ます。

ルーティングの設定を間違えると通常のページ遷移になり、せっかく上でfirebaseの初期化を最初の一回だけにしたのに意味がなくなります。

例えば以下のようなルーティングは、ページ遷移しますがクライエントサイドナビゲーションではなくなるので、注意してください。

// 一部抜粋
import { useRouter } from "next/router";
const router = useRouter();
router.push("profile/user1");

正しくはこうです。

// 一部抜粋
import { useRouter } from "next/router";
const router = useRouter();
router.push("profile/[username]", "profile/user1");

公式のドキュメントでも下の書き方で紹介されていますので間違えることは無いと思いますが、私みたいに適当に調べてやると上の書き方でエラーを出して心がコンフリクトを起こしてしまいますので気をつけてください。

後は細かい修正を加えると

スクリーンショット 2020-08-18 23.27.52

こんな感じです。新規登録や匿名認証だとすぐに反映されずリロードが必要になりますが、そこは追々デバッグしたいと思います。

ここまでのソースはgithubにあります。

これだけのために一日潰しました。idとパスワードをステートで持たせて遷移する度にログイン処理を自動で行う機能をつけようかといろいろ考えてましたが、上手く言ってよかったです。

今のところfirebaseAPIがフロントで機能するのでサーバサイドレンダリングを出来ないでいます。とはいえ認証以外の部分は(スマホも含め)爆速なので今のところは問題無いです。

チャットもリアルタイムなのでクライアントサイドでのレンダリングになるでしょうし、あまりnext.jsの良さは引き出せてないのかもしれません。
ここまで作ってnext.jsはチャットアプリのように通信して動的に画面が変化するものよりもブログやECサイトなどの方が力を発揮するような気がします。
とは言えこのアプリは最後まで完成させたいと思っています。後途中から口調が変わっていますね。

文章書くのも練習しなくては。

この記事が気に入ったらサポートをしてみませんか?