🙌

【 Next.js × FirebaseAuth 】初期化処理に若干のラグがあるらしい

2023/10/19に公開
4

解決しました

https://zenn.dev/hasumi/articles/a7f7524025c78b

過去悩んでいた内容

認証していないユーザーが認証しているユーザーにしか見れないページに飛ばれると困りますよね。

なので、『理想としては』
firebase を初期化をする非同期処理があって
初期化されたら認証しているかどうかで判断をしたいはず

ただここで問題なのがどこに初期化処理の非同期処理が無い事。

自分は Flutter をしていたので
Flutter だと main 関数にて下記の関数を発火させるだけで解決する

await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

※ Flutter WEB を使用する場合はこれだけでは足りない

Next.js ではどうするべきなのか?

こいつを使います↓

onAuthStateChanged

firebase の認証状態を常に監視して
変更されるごとに発火するタイプの関数ですね。

自分的には若干のラグがあるからあまり使いたくないけど
他になさそうな感じがするから使わざるおえないって感じですね。

ちなみに自分は下記の感じで使ってみました。

  useEffect(() => {
    onAuthStateChanged(auth, async (user) => {
      setUid(user?.uid);
    });
  }, []);

これでも0.5秒くらいラグがあって
user 情報が defined になってるから困ってる。
どうやって調理してやろうか

割と自分と同じ悩みの人発見

https://zenn.dev/darthrommy/articles/5d08ec7001b126

Discussion

Honey32Honey32

あまり詳しくないのですが、 Suspense 機能と、Next.js ではそれと組み合わせて使える use() という新機能が使えて、Promise をうまく扱えるようなので、それで行けるかもしれません。

const waitForAuthStateChanged = () => 
  new Promise<string | undefined>((resolve) => { 
    onAuthStateChanged(auth, async (user) => {
      resolve(user?.uid);
    });
  });
const [userPromise] = useState(() => waitForAuthStateChanged())
const user = use(userPromise);

「Server Component で作成した Promise を Client Component に渡せ」って書かれているので、たぶんuseState を使って Promise の作成を 1回だけにする(必要になれば、setter 関数も使って、その都度 set する)ことで対処できるかと思います。

https://ja.react.dev/reference/react/use

Honey32Honey32

https://zenn.dev/uhyo/articles/react-use-rfc-2#usememoだとだめなの?

やっぱりダメかもしれないです。

以下のような感じにして、

  • promise を作成して、子を Suspense で囲む親
  • use() で suspense を発生させながら取り出す(そのあとは useEffect で変更を監視する)子

で役割を分担すると実現できるかも。

"use client";
// これを公開する
export const UserSuspense = ({ children }) => {
   const [userPromise] = useState(() => waitForAuthStateChanged());
   return (
    <Suspense fallback={"ここにローディング状態の表示を入れる"}>
      <SuspenseProvider promise={userPromise}>
        {children}
      </SuspenseProvider>
    </Suspense>
    );
}

const SuspenseProvider = ({ promise, children }) => {
  const initialUser = use(promise)
  const [user, setUser] = useState(initialUser);

  useEffect(() => {
    onAuthStateChanged(auth, async (user) => {
      setUser(user?.uid);
    });
  }, []);

  return (
    <UserContext.Provider userId={user}>
       {children}
    </UserContext.Provider>
  );
}
Honey32Honey32

suspsne を使って初期ローディングするのは await auth.authStateReady で行けそうですね

ただ、ログアウトしたときや、ユーザーが切り替わったときにちゃんと反映するには、依然として useEffect + onAuthStateChanged が必要だと思います。