もふもふ技術部

IT技術系mofmofメディア

Next.js を使ってみる(TypeScript + firebase + Recoil + vercel) その1

バックエンドはRuby on Railsを主に使っていますがフロントエンドはフレームワークをあまりつかっていないことに気づいたので試してみる

next 9.5.5 react 17.0.1 recoil ^0.0.13 typescript ^4.0.5 firebase ^8.0.0

環境構築

Next.jsには Rails new のように雛形となるプロジェクトをコマンド経由で作成できるので、雛形となるプロジェクトを作成していきます。 yarnを使っていきます。

$ yarn create next-app プロジェクト名
$ yarn create next-app mofmof-box 

今回はmofmof-boxという名前で作っていきましょう。

作成が終わったらvscodeでプロジェクトを開きます

$ code mofmof-box

このタイミングで一度正しくプロジェクトを作成できているかを確認しておきます。

$ yarn dev

next.js welcom

アクセスして上記のような画面が表示されればオッケーです。デフォルトはlocalhost:3000となっています。

TypeScriptを導入する

Next.jsはコマンドで生成した段階ではJavaScriptで記載されています。 このままjsで開発してもいいですが型の恩恵をうけておきたいです。 無駄なエラーも防ぎたいのでTypeScriptを導入しましょう。

tsconfig.jsonを作成する

プロジェクト直下にtsconfig.jsonを作成します。その状態で yarn devを行うとコンソールにエラーが出ます。 typescriptを使いたい場合は下記コマンドを打てと言ってくるので、それに従います。

next-env.d.tsというファイルが作成されれば成功です。

また、create時に生成されたファイル群の中に.js拡張子のものがいくつかできてしまっているので、それは.tsxにしておきましょう。

諸々終わったらyarn devし直してもう一度起動します。エラーが消えてればオッケーです。

Firebaseでユーザーをいい感じに認証させる

ユーザー認証にはみんな大好きfirebaseを使います。今回はvercelを使うので、hostingのチェックは外して作りましょう。もちろんwebです

一通りの登録ができたら、Next.jsでfirebaseを使えるようにします。

$ yarn add firebase

設定ファイルを作成します。

lib/firebase.ts

import { firebase } from '@firebase/app'
import '@firebase/auth'
import '@firebase/firestore'

if( process.browser && firebase.apps.length === 0){
  const config = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
    measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
  }
  firebase.initializeApp(config)
}

NEXT_PUBLIC_というプレフィックスをつけるとブラウザでもその環境変数が利用できるとのことなので、大人しく従っておきましょう。

Next.jsにはデフォルトでdotenvライブラリが入っているので.env.localにでも上記の環境変数を入れておきます。

設定できたら、最後に_app.tsxfirebase.tsをインポートしておきましょう。

ユーザー認証

FirebaseAuthenticationを利用してユーザー認証を行う。

今回はメール認証も書くのちょっとだるいのでFirebaseAuthenticationはメインではないので匿名認証を使ってぱぱっと作っていく。どこかで書き換えやるかも。もしメール認証がしたかったらそっちを有効にしていきます。 SNS認証も提供してくれているが、SNS側にも色々設定を書いたりしないといけないのでここでは割愛。

hooks/auth.tsというファイルを作成して書いてみる

import { firebase } from '@firebase/app'

export const useAuthenticate = () => {
  firebase
    .auth()
    .signInAnonymously()
    .catch(function (e) {
      console.log(e.message)
    })

  firebase.auth().onAuthStateChanged(function (u) {
    if (u) {
      console.log(u.uid)
      console.log(u.isAnonymous)
    } else {
      // signed outの時の処理
    }
  })
}

if (process.browser) {
  authenticate()
}

こんな感じ。 process.browserでブラウザが開いてる時に認証を呼ぶ。もしemailでの認証ならsignInAnnonymously()の部分をcreateUserWithEmailAndPassword(email, pass)とすれば良いです。

ブラウザで検証ツールを用いて調べると

console.log(u.uid)
console.log(u.isAnonymous)

の記載によってuidとtrue(今回は匿名ログインなので)が帰ってくる。 確認できれば匿名ログインは成功している。

Recoilを使って状態保持を行う

今Reactを使っているプロジェクトはGraphQLを使用して状態管理を任せていますが、RESTの場合はRedux等を使って状態管理を行う。 とはいえ大きくない規模にReduxはオーバーな感じもあるので、今回はFB製のライブラリであるRecoilを使って状態を管理してみる。

ライブラリをインストール

$ yarn add recoil

使い方はRecoilを使うコンポーネントタグで囲めば良いらしい。

pages/_app.tsx

import { RecoilRoot } from 'recoil'
import '../styles/globals.css'
import '../lib/firebase'
import '../hooks/auth'

function MyApp({ Component, pageProps }) {

  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
    )
}

export default MyApp

Recoilにはatomという機能が提供されているらしい。 atomはstateの一部を表す事ができ、任意のコンポーネントから読み書きできる。atomの値を読み込むコンポーネントは暗黙の内にそのアトムをサブスクライブしているので、該当アトムを更新するとそのアトムをサブスクライブしているすべてのコンポーネントが再レンダリングされる。

みたいなことが公式に書いてあります。良い感じです。

公式の例を参考に、atomを実装してみる。

type User = {
  uid: string,
  isAnonymous: boolean
}

const userState = atom<User>({
  key: 'user',
  default: null
})

keyは他の名前とかぶらないようなユニークな名前をつけます。この名前で読み書き対象を指定できる。

defaultはデフォルト値。 この2つがatomオプションで必須の項目になるとのこと。

atomが指定できたら、Recoilでuserを管理できるようにauthenticate関数を少し変更していきます。

import { useEffect } from 'react'
import { firebase } from '@firebase/app'
import { atom, useRecoilState } from 'recoil'

type User = {
  uid: string,
  isAnonymous: boolean
}

const userState = atom<User>({
  key: 'userState',
  default: null
})

export const useAuthenticate = () => {

  const [user, setUser] = useRecoilState(userState)

  useEffect(() => {
    if (user) return 

    firebase
      .auth()
      .signInAnonymously()
      .catch((e) => {
        console.log(e.message)
      })

    firebase.auth().onAuthStateChanged((u) => {
      if (u) {
        console.log(u)
        setUser({uid: u.uid, isAnonymous: u.isAnonymous})
      } else {
        setUser(null)
      }
    })
  }, [])

  return user
}

authenticateメソッドは他の場所で使用できるようにuserを返すようにしておく。また、今回はブラウザを開いたときに匿名ログインを行うので、最初に一度だけ行うことにする。

index.tsx

import { useAuthenticate } from '../hooks/auth';

を行い、コンポーネントの中で

const user =  useAuthenticate()

として、importしたauthenticate関数の実行結果をuserに入れておきます。

<div>{user.uid)</div>

返すコンポーネントの中に上記のようなuidを表示するものを入れてみます。

uid

どこでもいいですが今回はページ下部に入れてみました。画像のように自分が要素を足した部分にuidが表示されていればオッケーです。

次回はデータベースをfirestoreで作ってデータを永続化したりしてみる