🐷

Remix / Firebase / Tailwind 環境構築する

2021/12/21に公開

環境変数を読むとかTailwind入れるとかあたり若干ややこしかったので、備忘録がてらRemixの環境構築について書きます。Firebaseのプロジェクトはあらかじめ作っておいてください。

Firebase導入まで

プロジェクト準備

このあたりを見ながらプロジェクトを立ち上げます。
https://remix.run/docs/en/v1/tutorials/blog

npx create-remix@latest
cd 作ったプロジェクト名
npm run dev

必要なパッケージも導入しておきましょう。

npm i dotenv firebase zustand
touch .env

デフォルトの npm run dev だと環境変数読めないので、devコマンドをちょっといじります。

package.json
  "scripts": {
    "build": "remix build",
-   "dev": "remix dev",
+   "dev": "node -r dotenv/config node_modules/.bin/remix dev",
    "postinstall": "remix setup node",

環境変数はこんな感じで埋めておきます。

FIREBASE_CLIENT_EMAIL=value
FIREBASE_PUBLIC_API_KEY=value
FIREBASE_AUTH_DOMAIN=value
FIREBASE_DATABASE_URL=value
FIREBASE_PROJECT_ID=value

Authentication

ログイン部分のUIはこちらにお任せするので入れておきましょう。
https://github.com/firebase/firebaseui-web-react

npm i --save react-firebaseui

コードをいじっていきます。

app/root.tsx
import {
  json,
  Links,
  LiveReload,
  LoaderFunction,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLoaderData,
} from "remix";

import { FirebaseOptions } from "firebase/app";
import initFirebase from "../utils/auth/initFirebase";
import { useAuth } from "../utils/auth/useAuth";

export const loader: LoaderFunction = () => {
  const config = {
    apiKey: process.env.FIREBASE_PUBLIC_API_KEY,
    authDomain: process.env.FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.FIREBASE_DATABASE_URL,
    projectId: process.env.FIREBASE_PROJECT_ID,
  };
  return json(config);
};

export default function App() {
  const config = useLoaderData<FirebaseOptions>();
  initFirebase(config);
  useAuth();

  return (
    <Document>
      <Layout>
        <Outlet />
      </Layout>
    </Document>
  );
}

function Document({
  children,
  title,
}: {
  children: React.ReactNode;
  title?: string;
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        {title ? <title>{title}</title> : null}
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
        {process.env.NODE_ENV === "development" && <LiveReload />}
      </body>
    </html>
  );
}

function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <div>
        <div className="container m-8">{children}</div>
      </div>
    </div>
  );
}

まだ作ってないコードのimportがあるあたりご留意ください。すぐ作ります。
ポイントはloaderで環境変数を読んでる点でしょうか。詳しくはこちらをご参照ください。
https://remix.run/docs/en/v1/guides/envvars

mkdir utils utils/auth
touch utils/auth/initFirebase.ts utils/auth/useAuth.ts

mkdir app/store
touch app/store/useStore.ts
initFirebase
import { initializeApp, FirebaseApp, getApp, FirebaseOptions } from 'firebase/app'

export default function initFirebase(config: FirebaseOptions): FirebaseApp {
  let app
  try {
    app = getApp()
  } catch (e) {
    app = initializeApp(config)
  }
  return app
}
useAuth.ts
import { useEffect } from "react"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import { useStore } from "../../app/store/useStore"

// オブザーバーを設定してユーザー情報をストアに入れる
export const useAuth = () => {
  const { setUser, removeUser } = useStore()
  const auth = getAuth()

  useEffect(() => {
    const cancelAuthListener = onAuthStateChanged(auth, (user) => {
      if (user) {
        setUser(user)
      } else {
        removeUser()
      }
    })

    return () => {
      cancelAuthListener()
    }
  }, [])

  return null
}
useStore.ts
import create from "zustand"
import { User } from "firebase/auth"

type UserStore = {
  user: User | null
  setUser: (user: User) => void
  removeUser: () => void
}

export const useStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user: User) => set(() => ({ user: user })),
  removeUser: () => set(() => ({ user: null })),
}))

これで一通り準備完了です。ログインできる画面を作りましょう。

touch app/routes/auth.tsx
auth.tsx
import * as React from "react"
import { useEffect, useState } from "react"
import StyledFirebaseAuth from "react-firebaseui/StyledFirebaseAuth"
import { GoogleAuthProvider } from "firebase/auth"
import * as firebaseui from "firebaseui"
import { getAuth } from "firebase/auth"

const firebaseAuthConfig: firebaseui.auth.Config = {
  signInFlow: "popup",
  signInOptions: [
    {
      provider: GoogleAuthProvider.PROVIDER_ID,
      requireDisplayName: false,
    },
  ],
  signInSuccessUrl: "/",
  credentialHelper: "none",
  callbacks: {
    signInSuccessWithAuthResult: ({ user }, redirectUrl) => {
      return true
    },
  },
}

const Index = () => {
  const auth = getAuth()
  const [renderAuth, setRenderAuth] = useState(false)
  useEffect(() => {
    if (typeof window !== "undefined") {
      setRenderAuth(true)
    }
  }, [])
  return (
    <div>
      {renderAuth ? (
        <StyledFirebaseAuth uiConfig={firebaseAuthConfig} firebaseAuth={auth} />
      ) : null}
    </div>
  )
}

export default Index

トップから認証ページに遷移できるようにします。あとログインしてたらid表示するようにしちゃいましょうか。

routes
import { Link } from "remix";
import { useNavigate } from "react-router";
import { useStore } from "../store/useStore";

export default function Index() {
  const { user } = useStore();
  const navigate = useNavigate();

  return (
    <div>
      <main>
        {user && <p>{user.uid}</p>}
        <Link to="/auth">to auth</Link>
      </main>
    </div>
  );
}

以上でログイン周辺の実装が完了です。

Tailwind導入

続けてもう一つの目的であるTailwindを導入しましょう。

といっても正直こちらの記事がめっちゃいいのでこちらを参考にしてください。一点、package.jsonのスクリプトだけdotenvの兼ね合いで調整が必要なのでご留意ください。

https://zenn.dev/gyarudev/articles/1e222f553c40a9

一通り導入が済んだら、package.jsonを開きます。scriptsのうち、devを調整しましょう。

package.json
  "scripts": {
    "dev": "node -r dotenv/config node_modules/.bin/remix dev & concurrently \"npm run dev:css\"",

これで環境変数を読みつつTailwindも適用することができます。

というわけで最後はあまり書くことがなかったんですが、一通り開発の準備を整えることができました。

Discussion