Firebase を初期化するちょっといい方法
前置き
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 kB
→ 227 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 ページのパフォーマンスを改善するためのちょっとした方法をご紹介しました。
少しの違いかもしれませんが、こういうことの積み重ねが大事かもしれませんね。
ご意見・質問等あればコメントください。
Discussion