firebase auth
shadcn ui でbuttonを入れると追加されたコンポーネントは、モジュール '@radix-ui/react-slot' またはそれに対応する型宣言が見つかりません。
とエラーが出るので、一度@radix-ui/react-slot
を削除してから、再度入れ直せば解決する。
pnpm remove @radix-ui/react-slot
pnpm add @radix-ui/react-slot
LINE認証をFirebase Authと連携させたい。LINEのログインはOIDCをサポートしているので、Firebase AuthをIdentity Platformにアップグレードして対応する。
記事で_app.tsxにAuthProviderを埋め込むとあったので、それに沿って実装していたら、NextJS14から_app.tsxはないことが判明。代わりにlayout.tsxかtemplate.tsxを使用するとのことなので、layout.tsxに実装することにした。
'use client'
import { auth, signIn, signInWithGoogle, signInWithLine, signOut } from '@/libs/firebase/client/auth'
import { deepEqual } from 'fast-equals'
import { User } from 'firebase/auth'
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
type AuthContextValue = {
user?: User
signIn: (email: string, password: string) => Promise<void>
signInWithGoogle: () => Promise<void>
signInWithLine: () => Promise<void>
signOut: () => Promise<void>
}
type AuthProviderProps = {
children?: ReactNode
}
const errorHandle = () => Promise.reject(new Error('親コンポーネントのツリー内にAuthProviderが見当たりません。'))
const AuthContext = createContext<AuthContextValue>({
signIn: errorHandle,
signInWithGoogle: errorHandle,
signInWithLine: errorHandle,
signOut: errorHandle,
})
export const useAuthContext = () => useContext(AuthContext)
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User>()
useEffect(() => {
const unsubscribed = auth.onAuthStateChanged((_user) => {
if (!deepEqual(user, _user)) {
if (_user) {
setUser(_user)
} else {
setUser(undefined)
}
}
})
return () => {
unsubscribed()
}
}, []) // <= 第二引数の[]がないと、onAuthStateChangedが2回呼ばれる。
return <AuthContext.Provider value={{ user, signIn, signInWithGoogle, signInWithLine, signOut }}>{children}</AuthContext.Provider>
}
import { AuthProvider } from '@/libs/components/providers/auth'
import { cn } from '@/libs/utils'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="ja">
<body className={cn('min-h-screen bg-background font-sans antialiased', inter.variable)}>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
)
}
Next.jsのappルートにした場合_appが使えなくなり、代わりにtemplateとlayoutになった。 そのため、共有のレイアウト、ページ全体に反映させたい場合はtemplateまたは、layoutを使うことで解決する。
templateとlayoutの違い
ページ全体に適用させるtemplateとlayoutの違いは再レンダリングするか、しないかの違いがある。ページ移行時、layoutは再レンダリングせず。templateは再レンダリングされる。
そのため、パフォーマンスを考慮するならlayoutを使う方がよい。
LINE認証を試みたら、This channel is now developing status. User need to have developer role.
と出た。チャンネルのステータスが開発中になっている場合は、ユーザーをそのチャンネルに招待して、権限を割り当てる必要があるとのこと。
Tester
の権限を割り当てて招待したら、認証ができるようになった。
また、招待ユーザーはLINEのビジネスアカウントで必要があるっぽい。
サーバーアクション内でfirebase adminを読んだら、TypeError: Cannot read properties of undefined (reading 'INTERNAL')
と出た。firebase-admin
の初期化するときのimportの仕方違っていた。
- import { initializeApp } from 'firebase-admin'
+ import admin from 'firebase-admin'
- export const app = initializeApp()
+ export const app = admin.initializeApp()
TypescriptのArray型で配列が空かどうかの判別をarray.length === 0
でやるのダサいなー。
Date型のフォーマットがめんどくさいので、datjsを入れる。
Firebase Adminからid tokenを取得することはできないっぽい。
クライアントから送信されたトークンを検証することはできる。
そのため、未認証のユーザーがデータを作成する際には、Firebase Adminを使用して作成させるか、サーバー匿名ユーザーを作成してクライアントからFirebaseのAPIを叩くかの二択になりそう。
新規アカウントを作成する場合はAuthも触るので、サーバー側で行わせたい。
認証後のデータ作成・更新はクライアントから行う。その際に、Firestoreルールを厳格に記述する。
また、ルールないでid tokenの期限チェックも行なってくれるので、サーバー側で実装する手間が減る。
Firebase ID トークンはステートレス JWT であるため、トークンの取り消しの判定は、Firebase Authentication バックエンドからトークンのステータスを要求するしかありません。そのため、このチェックをサーバーで実行すると、ネットワークで追加のラウンドトリップを必要とする負荷の大きい処理となります。このネットワーク リクエストは、Admin SDK を使用してチェックを行うのではなく、取り消しを確認する Firebase セキュリティ ルールを設定することで回避できます。
クラアントライブラリのFirestoreを使いたいからServerComponentsの選択肢はないな。
もしServerComponentsを使うなら、firebase admin sdkを使うことになる。
となると、SWRを使用することになるのかな。
しばらくの間は、ClientComponentsで使う時はSWRなどを使用するのが良さそう。そう考えると、fetchは現時点ではSWRを使うか、ServerComponentsに入れてしまうのがベターだろう。
SWR+Suspenceで使うことは推奨されていないな。
ということは、firestore sdkでSuspenceを利用することはできないのか。
SWR ではサスペンスはまだ実験段階なので、ベータ機能を使用するリスクがあります。以下の理由により、安定させることはできません。
まだすべての SWR 機能をカバーできるわけではありません。たとえば、ウォーターフォールや依存フェッチの回避などです。
SSR でのデータ取得ユースケースを十分にカバーすることはできません (多くのフレームワークがカバーしています)。以下を参照してください。
React 18はメジャーバージョンの変更であり、@stefee前述の通り、特にサスペンスの動作は変わります。17から18にアップグレードすると、
SSR 中にサスペンスが実行されるので、サーバー側で SWR リクエストが機能することを確認してください。
SSR 中に Suspense が実行されるため、レンダリングされた結果もハイドレーションされます。要求されたデータとレンダリングされたデータが SSR とハイドレーションの間で変更されると、ハイドレーション不一致エラーが発生します。そのため、Suspense + SSR を使用して動的データを取得しないようにしてください。
エラーコードのマップを使わないで、文字列でチェックした方が良いのか。
SDK がスローするエラーを簡単に比較するための Auth エラーコードのマップ。
マップ内の個々のキーをツリー シェイクすることはできないため、マップを使用するとバンドルサイズが大幅に増加する可能性があります。
5回間違えたら、アカウントをロックしてくれるはずだが、リンク切れを起こしていて現状どうなのか分からない。
また、パスワードを5回間違えると、以下のエラーが返ってきます。
何度もパスワードを間違えた
errorCode: auth/too-many-requests
message: Access to this account has been temporarily disabled due to many failed login attempts. You can immediately restore it by resetting your password or you can try again later.
https://firebase.google.com/docs/reference/js/firebase.auth.Error
ChatGPT先生に聞いた感じ、auth/too-many-requests
のエラーが発生した場合、Authの該当アカウントはロックされるが、具体的に、どれぐらいの回数のリクエストを送信したらロックされるかのドキュメントはないらしい。また、一定時の時間が経過すれば自動的にアカウントロックは解除されるが、手動で解除することは不可能らしい。
Firebase Authenticationのエラーコードauth/too-many-requestsは、短時間に過剰な数のリクエストを行った場合に発生しますが、具体的な回数は公開されていません。このエラーは、不正使用やスパムを防ぐためのレートリミットによるもので、特定の回数を超えると発生します。
auth/too-many-requestsエラーが発生した場合、Firebase Authenticationは過剰なリクエストを受けたアカウントを一時的にロックすることになります。これは不正使用やスパム行為からアカウントを保護するための措置です。
Firebase Authenticationでは、管理者が特定のアカウントのロックを解除するための直接的な機能は提供されていません。auth/too-many-requestsエラーは一時的なものであり、時間が経過すれば自動的に解除されます。
hsl(H S L[ / A])
のA
はアルファ値を示しており、省略可能。
Suspenseはサーバーコンポーネント専用だと感じた。
クライアントコンポーネントでも利用できるらしいが、無限にレンダリングし、
throw promise
が一向に解決されない。
object-fit: cover;
を適応すれば、画像のアスペクト比を保ったまま、サイズを合わせられる。
また、親コンポーネントでoverflow: hidden;
が適用されていれば、border-radius
したときにはみ出した部分を非表示にすることができる。
あれ?object-fit: contain;
の方がしっくりくるな。
置換コンテンツはアスペクト比を維持したまま、要素のコンテンツボックス全体を埋めるように拡大縮小されます。オブジェクトのアスペクト比がボックスのアスペクト比と合わない場合は、オブジェクトの方が合うように切り取られます。
sharpね。あとで試してみよう。
⚠ For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended. Run 'npm i sharp', and Next.js will use it automatically for Image Optimization.
next.config.mjs
内でnext.config.mjs Parsing error: Cannot find module 'next/babel'
が発生していた。.vscode/setting.jsonに以下を追加すれば解決することがわかった。
"eslint.workingDirectories": ["next-app"]
原因としては、Nextアプリのディレクトリをサブディレクトリにしていたことで、Eslintが管理先のディレクトリを見失う事が原因らしい。
firebaseにデプロイした時に、png画像が404で取得できない。
next.config.mjs
の設定で画像の最適化をOFFにすれば、解決する。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
images: {
unoptimized: true,
},
}
export default nextConfig
色々調べたけど、解決できないから、
もし画像の最適化をしたいなら、apiを用意してローダーで解決しろってことなんだろうな。
アセット画像を全てsvgで用意すれば上記のような問題は発生しないから、画像はsvgにしてくれると助かる。
外部アカウントでサインアップする際に、firestoreにアカウントデータが保存されていない場合、新規作成をし、アカウントが作成できたら、画面遷移をする。
firebase auth とfirestoreにアカウントデータの作成タイミングが異なるので、user
があるかどうかだけだと、firestoreにアカウントデータが作成される前に画面遷移できてしまう。
そのため、アカウントデータをonSnapshot
を使って監視するのだが、onSnapshot
はコミットされていなくても発火するため、以下のように工夫が必要。
const unsubscribe = onSnapshot(docRef, {
next: async (snapshot) => {
if (!snapshot.metadata.hasPendingWrites) { // trueの時、コミット中ということ
observe(snapshot.data())
}
},
})
(property) SnapshotMetadata.hasPendingWrites: boolean
スナップショットに、まだバックエンドにコミットされていないローカル書き込み (例えば set() や update() 呼び出し) の結果が含まれている場合、true を返します。リスナーが(SnapshotListenOptionsを介して)メタデータの更新をオプトインしている場合、書き込みがバックエンドにコミットされると、hasPendingWritesがfalseに等しい別のスナップショットを受け取ります。