React Hook Form、Zodで職務経歴書登録フォーム開発(準備編)
モチベーション
- React(TypeScript) + Firebaseでメモアプリ開発 で登録機能を開発したものの、入力項目も少ないのでフォーム部品、バリデーション部品は利用していなかった
- 今回はある程度の入力項目数の要件を想定し、React Hook Form、Zodを試して使い方を学ぶのが目的
- ついでなのでNext.jsをFirebaseにホスティングしてみる
- React(TypeScript) + Firebaseでメモアプリ開発 ではNext.jsは使っていなかったので
今回使用したライブラリーのバージョン
- Next.js: 13.4.19
- 最新は13.5.3でしたが後述のエラーが発生したためダウングレード
- React: 18.2.0
- React Hook Form: 次回導入
- Zod: 次回導入
- MUI: 5.14.11
- TailwindCSS: 3.3.3
- Recoil: 0.7.7
- TypeScript: 5.2.2
- Firebase: 10.4.0
今回行ったことまとめ
- Next.jsにMUIのカスタムテーマを適用
- サインイン機能開発
- FirebaseにNext.jsアプリをホスティング
- GitHub、Firebase連携
React Hook Form、Zodはまだ使っていません!(タイトル詐欺)
Next.jsにMUIのカスタムテーマを適用
Using Material UI with a custom theme に従い Theme Registry
を作成し、 RootLayout
の中で利用するようにしました。
カラーパレットにカスタムカラーを定義
今回、一部のコンポーネントだけ他と色合いを変更しようと思い、Custom colors を参考に以下のようにカスタムカラーを定義してみました。
import { createTheme } from "@mui/material";
// PaletteとPaletteOptionsにキーを追加
declare module "@mui/material/styles" {
interface Palette {
blue: Palette["primary"];
}
interface PaletteOptions {
blue?: PaletteOptions["primary"];
}
}
export const theme = createTheme({
palette: {
primary: {
main: "#ff0000",
},
secondary: {
main: "#ffffff",
},
// 新しい色を定義
blue: {
main: "#4285f4",
light: "#8ab4f8",
dark: "#0d47a1",
contrastText: "#FFFFFF",
},
},
typography: {
fontFamily:
"Noto Sans JP, Yu Gothic, Meiryo, Hiragino Kaku Gothic Pro, sans-serif",
button: {
textTransform: "none",
},
},
});
サインイン機能開発
基本的には Vite + React + FirebaseでGoogle認証機能を作ったときのメモ と同じように開発しましたが、いくつか注意すべき点があったので以下に書きます。
ViteとNext.jsとでは環境変数の命名ルールや読み込み方が異なる
Viteでは以下のようにして環境変数を読み込んでいました。
- 環境変数名の初めには
VITE_
を付与する -
import.meta.env.{環境変数名}
でその環境変数の値を利用する
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
mesurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
};
一方、Next.jsでは以下のようにする必要があります。
- クライアントで環境変数を利用する場合は環境変数名の初めには
NEXT_PUBLIC_
を付与する -
process.env.{環境変数名}
でその環境変数名の値を利用する
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
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,
mesurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};
当初 NEXT_PUBLIC_
を付与しておらず「環境変数取れないなぁ。なんでだろう?」と若干悩んでいましたが、 Next.jsにおける環境変数 (env) の取り扱い を参照し解決しました。
useRouterは "next/navigation" のものを使うべし
ページ遷移する際に import { useRouter } from "next/navigation"
ではなく import { useRouter } from "next/router"
を利用していて NextRouter was not mounted Next.JS
というエラーが出ていました。よく見ずにVSCodeの補完機能に頼っているとこうなりそうです(なった)。
App Routerの場合、"next/navigation" を使う必要がある旨は Nnxt 13 appディレクトリでuseRouterを使用するとエラーが表示される などにも書かれていました。
Hydratingエラー
当初、Recoilで管理する状態(ログインユーザー)の初期値を以下のように設定していました。ローカルストレージに値があればそれを利用するようにしています。
export const userAtom = atom({
key: "userAtom",
default: {
userId: localStorage.getItem("userId") || null,
userName: localStorage.getItem("userName") || null,
} as LoginUser,
});
ただ、これだとサーバーサイドで生成するHTMLとクライアントサイドで生成するHTMLに差が生まれてしまいます。サーバーサイドではローカルストレージは利用できないためです。これにより以下のエラーが発生しました。
react-dom.development.js:15468 Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
これを回避するため、初期値はnullとしてこの状態を利用するコンポーネントでuseEffectを利用しローカルストレージから値を取得するようにしました。Bingにそう勧められたのでやってみましたが、なぜこれでいいのかはイマイチわかっていないのでApp Routerを真面目に勉強します・・・。
useEffect(() => {
let _userId = localStorage.getItem("userId");
let _userName = localStorage.getItem("userName");
if (loginUser.userId) {
_userId = loginUser.userId;
}
if (loginUser.userName) {
_userName = loginUser.userName;
}
setLoginUser((prev: LoginUser) => {
return {
...prev,
userId: _userId || null,
userName: _userName || null,
};
});
}, [loginUser.userId, loginUser.userName, setLoginUser]);
FirebaseにNext.jsアプリをホスティング
Next.js を統合する に書かれていますが、フレームワーク対応の Hosting は早期公開プレビュー版です とのことです(だから後述のエラーも発生したのかなと想像)。
ウェブフレームワークのプレビューを有効にする
手順は Next.js を統合する の通りでまずはウェブフレームワークのプレビューを有効にします。
firebase experiments:enable webframeworks
初期化コマンドを実行
ここはいつも通り firebase init
や firebase init hosting
を実行するだけなので詳細割愛します。
デプロイ -> エラー発生
次に firebase deploy
コマンドを実行したところ以下のエラーが発生しました。
TypeError: Cannot destructure property 'customConfig' of '(intermediate value)(intermediate value)(intermediate value)' as it is null.
https://github.com/firebase/firebase-tools/issues/6382 に書かれていますが、どうもNext.jsが13.5以上だとエラーになるようなので、13.4.19にダウングレードしエラー解消しました。
GitHub、Firebase連携
これも基本は Firebase、GitHub Actions連携 と同じですが、1つだけ注意点がありました。なりゆきだとGitHub ActionsでFirebaseにデプロイする際に以下のエラーが発生します。
Error: Cannot deploy a web framework from source because the experiment webframeworks is not enabled. To enable add a FIREBASE_CLI_EXPERIMENTS environment variable to .github/workflows/firebase-hosting-merge.yml, like so:
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
...
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
エラーメッセージ通り環境変数を追加するとエラー解消します。以下修正後のワークフローです。
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on merge
"on":
push:
branches:
- main
jobs:
build_and_deploy:
runs-on: ubuntu-latest
env: # ここを追加
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.FIREBASE_MEASUREMENT_ID }}
FIREBASE_CLI_EXPERIMENTS: webframeworks
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: "${{ secrets.GITHUB_TOKEN }}"
firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_NEXTJS_A609C }}"
channelId: live
projectId: nextjs-a609c
コード
アプリケーション
次回予定
ここまででお腹いっぱいなので、タイトル回収(React Hook Form、Zodの利用)は次回に持ち越します!
Discussion