Firebase を初期化するちょっといい方法

8 min読了の目安(約7200字TECH技術記事
Likes11

前置き

Firebase 便利ですよね。
手軽にいろいろな機能が使えるので、とっても重宝しています。

最近、 Next.js のサンプルプロジェクトを見ていて、 Firebase の初期化部分が気になったのでこの記事を書いてみました。

なお、特に Next.js に限った話ではないです。

サンプルプロジェクトの場合

はじめに、問題のサンプルプロジェクトがどうやっているかを確認しましょう。

説明のためと趣味の問題で、コードはサンプルプロジェクトのままではなく簡略化しています。

  • firebaseClient.js
import firebase from 'firebase/app'
// 必要に応じて追加する
import 'firebase/auth'
import 'firebase/firestore'
import 'firebase/storage'
import 'firebase/analytics'

if (typeof window !== 'undefined' && !firebase.apps.length) {
  firebase.initializeApp(JSON.parse(process.env.NEXT_PUBLIC_FIREBASE_CONFIG))
  // モジュールごとの初期化
  firebase.analytics()
}

export default firebase

使用する側は簡単です。

  • pages/index.js
import firebase from '../firebaseClient'

...
  const [userName, setUserName] = useState("")

  useEffect(() => {
    return firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        setUserName(user.displayName)
      } else {
        setUserName("Guest")
      }
    })
  }, [])

何が問題なの?

ぱっと見て何もおかしなことはなさそうです。しかし問題があります。

それは、初期ロードの重さです。

Next.js はビルドするだけでファイルサイズがわかるので確認してみましょう。
上記の方法で Firebase を導入する前後を比較してみましょう。

  • Firebase 導入前 (create-next-app 直後)
Page                                                           Size     First Load JS
┌ ○ /                                                          3.4 kB         61.9 kB
├   └ css/49498b39c9442f323640.css                             703 B
├   /_app                                                      0 B            58.5 kB
├ ○ /404                                                       3.44 kB        61.9 kB
└ λ /api/hello                                                 0 B            58.5 kB
+ First Load JS shared by all                                  58.5 kB
  ├ chunks/f6078781a05fe1bcb0902d23dbbb2662c8d200b3.b1b405.js  10.3 kB
  ├ chunks/framework.cb05d5.js                                 39.9 kB
  ├ chunks/main.a140d5.js                                      7.28 kB
  ├ chunks/pages/_app.eb1f7b.js                                281 B
  ├ chunks/webpack.e06743.js                                   751 B
  └ css/9a27f53514ad25d68166.css                               194 B
  • Firebase 導入後
Page                                                           Size     First Load JS
┌ ○ /                                                          169 kB          227 kB
├   └ css/49498b39c9442f323640.css                             703 B
├   /_app                                                      0 B            58.5 kB
├ ○ /404                                                       3.44 kB        61.9 kB
└ λ /api/hello                                                 0 B            58.5 kB
+ First Load JS shared by all                                  58.5 kB
  ├ chunks/f6078781a05fe1bcb0902d23dbbb2662c8d200b3.b1b405.js  10.3 kB
  ├ chunks/framework.cb05d5.js                                 39.9 kB
  ├ chunks/main.cf623a.js                                      7.28 kB
  ├ chunks/pages/_app.ee6a9c.js                                282 B
  ├ chunks/webpack.e06743.js                                   751 B
  └ css/9a27f53514ad25d68166.css                               194 B

注目すべきは、一番右上の数字が 61.9 kB227 kB になっています。
ここではわからないですが、ターミナル上での数字の表示が緑色 (良好) から赤色 (警告) に変わりました。

よくなさそうですね。

ちなみに、 Firebase のモジュールを追加するとさらに重くなります。地味にきついかもしれません。

Dynamic import を使用する場合

では、どのようにするのがよいでしょうか。

解決するために、 Dynamic import の仕組みを使って Firebase の各モジュールの読み込みを遅延させます。

コードは以下のような感じです。
(今回は React Context を使ってみました。)

初期化を Promise でラップして、その中で import を呼んでいるのがポイントです。

  • FirebaseContext.js
import React, { createContext, useEffect, useState } from 'react'
import firebase from 'firebase/app'

const FirebaseContxt = createContext({
  firebase: null,
  getFirebase: async () => null,
})

const initialize = async () => {
  if (typeof window === 'undefined') return

  await Promise.all([
      // 必要に応じて追加する
      import('firebase/auth'),
      import('firebase/firestore'),
      import('firebase/storage'),
      import("firebase/analytics"),
    ])

  firebase.initializeApp(JSON.parse(process.env.NEXT_PUBLIC_FIREBASE_CONFIG))
  // モジュールごとの初期化
  firebase.analytics()

  return firebase
}
const promise = initialize()

const FirebaseContxtProvider = ({ children }) => {
  const [firebase, setFirebase] = useState(null)

  useEffect(() => {
    ;(async () => {
      setFirebase(await promise)
    })()
  }, [])

  return (
    <FirebaseContxt.Provider value={{ firebase, getFirebase: () => promise }}>
      {children}
    </FirebaseContxt.Provider>
  )
}

export default FirebaseContxt
export { FirebaseContxtProvider }

どこでも FirebaseContext を使えるように、ルートにプロバイダを配置します。

  • pages/_app.js
import { FirebaseContxtProvider } from '../FirebaseContext'
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return (
    <FirebaseContxtProvider>
      <Component {...pageProps} />
    </FirebaseContxtProvider>
  )
}

export default MyApp

使用する側は少し面倒ですが、 FirebaseContext から取得します。
firebase は初回は null であることに注意します。

  • pages/index.js
import FirebaseContext from '../FirebaseContext'

...

  const { firebase } = useContext(FirebaseContext)
  const [userName, setUserName] = useState("")

  useEffect(() => {
    if (firebase) {
      return firebase.auth().onAuthStateChanged((user) => {
        if (user) {
          setUserName(user.displayName)
        } else {
          setUserName("Guest")
        }
      })
    }
  }, [firebase])

ユーザイベント等で firebase を使いたいときは、 getFirebase の方を利用します。

  const { getFirebase } = useContext(FirebaseContext)
  
  const logout = async () => {
    const firebase = await getFirebase()
    await firebase.auth().signOut()
  }

初回ロードサイズはどうなったか

ビルドして初回ロードのサイズを見てみましょう。

Page                                                           Size     First Load JS
┌ ○ /                                                          2.82 kB        74.6 kB
├   └ css/49498b39c9442f323640.css                             703 B
├   /_app                                                      0 B            71.8 kB
├ ○ /404                                                       2.75 kB        74.6 kB
└ λ /api/hello                                                 0 B            71.8 kB
+ First Load JS shared by all                                  71.8 kB
  ├ chunks/71247caf95475e3ea7f9a0f8a30beb258b23d005.844417.js  12.3 kB
  ├ chunks/commons.4d7c61.js                                   3.24 kB
  ├ chunks/f6078781a05fe1bcb0902d23dbbb2662c8d200b3.957259.js  7.86 kB
  ├ chunks/framework.9ec1f7.js                                 39.9 kB
  ├ chunks/main.22b411.js                                      6.97 kB
  ├ chunks/pages/_app.17b85c.js                                300 B
  ├ chunks/webpack.2a8fbb.js                                   1.3 kB
  └ css/9a27f53514ad25d68166.css                               194 B

先程 227 kB だったものが 74.6 kB になりました。
やったね!!

さいごに

今回は、 Firebase を使うにあたって Web ページのパフォーマンスを改善するためのちょっとした方法をご紹介しました。
少しの違いかもしれませんが、こういうことの積み重ねが大事かもしれませんね。

ご意見・質問等あればコメントください。