Firebase を Next.js アプリと統合する

1. 始める前に

この Codelab では、レストラン レビューのウェブサイトである Friendly Eats という Next.js ウェブアプリに Firebase を統合する方法を学びます。

Friendly Eats ウェブアプリ

完成したウェブアプリは便利な機能を備えており、Firebase が Next.js アプリの構築にどのように役立つかを実証しています。主な機能は次のとおりです。

  • Google でログインとログアウト機能: 完成したウェブアプリでは、Google でログインし、ログアウトできます。ユーザーのログインと永続性は、完全に Firebase Authentication で管理されます。
  • 画像: 完成したウェブアプリでは、ログインしたユーザーがレストランの画像をアップロードできます。画像アセットは Cloud Storage for Firebase に保存されます。Firebase JavaScript SDK には、アップロードされた画像への公開 URL が用意されています。この公開 URL は、Cloud Firestore の関連するレストランのドキュメントに保存されます。
  • レビュー: 完成したウェブアプリでは、ログイン済みのユーザーが、星による評価とテキストベースのメッセージで構成されるレストランのレビューを投稿できます。レビュー情報は Cloud Firestore に保存されます。
  • フィルタ: 完成したウェブアプリでは、ログイン ユーザーがカテゴリ、場所、価格に基づいてレストランのリストをフィルタできます。使用する並べ替え方法をカスタマイズすることもできます。データには Cloud Firestore からアクセスされ、使用されたフィルタに基づいて Firestore のクエリが適用されます。

前提条件

  • Next.js と JavaScript に関する知識

学習内容

  • Firebase で Next.js アプリルーターおよびサーバーサイド レンダリングを使用する方法
  • Cloud Storage for Firebase で画像を保持する方法。
  • Cloud Firestore データベースでデータの読み取りと書き込みを行う方法。
  • Firebase JavaScript SDK で「Google でログイン」を使用する方法。

必要なもの

  • Git
  • Java 開発キット
  • Node.js の最新の安定版
  • 任意のブラウザ(Google Chrome など)
  • コードエディタとターミナルがある開発環境
  • Firebase プロジェクトを作成、管理するための Google アカウント
  • Firebase プロジェクトを Blaze 料金プランにアップグレードできる

2. 開発環境を設定する

この Codelab では、アプリのスターター コードベースを提供し、Firebase CLI を使用します。

リポジトリをダウンロードする

  1. ターミナルで、Codelab の GitHub リポジトリのクローンを作成します。
    git clone https://github.com/firebase/friendlyeats-web.git
    
  2. GitHub リポジトリには複数のプラットフォーム用のサンプル プロジェクトが含まれています。ただし、この Codelab では nextjs-start ディレクトリのみを使用します。次のディレクトリをメモします。
    • nextjs-start: ビルドのベースとなるスターター コードが含まれます。
    • nextjs-end: 完成したウェブアプリの解答コードが含まれます。
  3. ターミナルで nextjs-start ディレクトリに移動し、必要な依存関係をインストールします。
    cd friendlyeats-web/nextjs-start
    npm install
    

Firebase CLI をインストールまたは更新する

次のコマンドを実行して、Firebase CLI がインストールされ、v12.5.4 以降であることを確認します。

firebase --version
  • Firebase CLI がインストールされているが、v12.5.4 以降でない場合は、次の手順で更新します。
    npm update -g firebase-tools
    
  • Firebase CLI がインストールされていない場合は、
    npm install -g firebase-tools
    
    インストールします。

権限エラーが原因で Firebase CLI をインストールできない場合は、npm のドキュメントを参照するか、別のインストール オプションを使用してください。

Firebase にログインする

  1. 次のコマンドを実行して、Firebase CLI にログインします。
    firebase login
    
  2. Firebase でデータを収集するかどうかに応じて、「Y」または「N」と入力します。
  3. ブラウザで Google アカウントを選択し、[許可] をクリックします。

3. Firebase プロジェクトを設定する

このセクションでは、Firebase プロジェクトをセットアップして、Firebase ウェブアプリをプロジェクトに関連付けます。また、サンプル ウェブアプリで使用される Firebase サービスも設定します。

Firebase プロジェクトを作成する

  1. Firebase コンソールで [プロジェクトを作成] をクリックします。
  2. [プロジェクト名を入力してください] テキスト ボックスに「FriendlyEats Codelab」(または任意のプロジェクト名)を入力し、[続行] をクリックします。
  3. この Codelab では Google アナリティクスは必要ないため、[このプロジェクトで Google アナリティクスを有効にする] オプションをオフにします。
  4. [プロジェクトの作成] をクリックします。
  5. プロジェクトがプロビジョニングされるまで待ってから、[続行] をクリックします。
  6. Firebase プロジェクトで、[プロジェクトの設定] に移動します。後で必要になるため、プロジェクト ID をメモしておきます。この一意の識別子によってプロジェクトが識別されます(Firebase CLI などで)。

Firebase プロジェクトにウェブアプリを追加する

  1. Firebase プロジェクトの [プロジェクトの概要] に移動し、e41f2efdd9539c31.png [ウェブ] をクリックします。
  2. [アプリのニックネーム] テキスト ボックスに、覚えやすいアプリのニックネーム(My Next.js app など)を入力します。
  3. [このアプリ用の Firebase Hosting も設定します] チェックボックスをオンにします。
  4. [アプリの登録] > [次へ] > [次へ] > [コンソールに進む] をクリックします。

Firebase のお支払いプランをアップグレードする

ウェブ フレームワークを使用するには、Firebase プロジェクトが Blaze 料金プランを利用している(Cloud 請求先アカウントに関連付けられている)必要があります。

  • Cloud 請求先アカウントには、クレジット カードなどのお支払い方法が必要です。
  • Firebase と Google Cloud を初めて使用する場合は、$300 分のクレジットと無料トライアルの Cloud 請求先アカウントの対象かどうかを確認してください。

ただし、この Codelab を完了しても、実際の料金は発生しません。

プロジェクトを Blaze プランにアップグレードする手順は次のとおりです。

  1. Firebase コンソールで、プランのアップグレードを選択します。
  2. ダイアログで Blaze プランを選択し、画面の指示に従ってプロジェクトを Cloud 請求先アカウントに関連付けます。
    Cloud 請求先アカウントを作成する必要がある場合は、アップグレードに戻ります。フローに沿って、アップグレードを完了します。

Firebase コンソールで Firebase サービスを設定する

Authentication を設定する

  1. Firebase コンソールで [認証] に移動します。
  2. [Get started] をクリックします。
  3. [その他のプロバイダ] 列で、[Google] > [有効にする] をクリックします。
  4. [プロジェクトの公開名] テキスト ボックスに、My Next.js app などの覚えやすい名前を入力します。
  5. [プロジェクトのサポートメール] プルダウンから、メールアドレスを選択します。
  6. [保存] をクリックします。

Cloud Firestore を設定する

  1. Firebase コンソールで、Firestore に移動します。
  2. [データベースを作成] > [テストモードで開始] > [次へ] をクリックします。
    この Codelab の後半で、データを保護するためのセキュリティ ルールを追加します。データベースにセキュリティ ルールを追加せずに、アプリを配布または公開しないでください。
  3. デフォルトの場所を使用するか、任意の場所を選択します。
    実際のアプリでは、ユーザーに近い場所を選択します。このロケーションは後で変更できません。これは自動的にデフォルトの Cloud Storage バケットのロケーションになります(次のステップ)。
  4. [Done] をクリックします。

Cloud Storage for Firebase を設定する

  1. Firebase コンソールで [ストレージ] に移動します。
  2. [開始] > [テストモードで開始] > [次へ] をクリックします。
    この Codelab の後半で、データを保護するためのセキュリティ ルールを追加します。Storage バケットのセキュリティ ルールを追加せずに、アプリを配布または公開しないでください。
  3. バケットのロケーションはすでに選択されているはずです(前のステップで Firestore を設定したため)。
  4. [Done] をクリックします。

4. スターター コードベースを確認する

このセクションでは、この Codelab で機能を追加する、アプリのスターター コードベースのいくつかの領域を確認します。

フォルダとファイルの構造

次の表に、アプリのフォルダとファイルの構造の概要を示します。

フォルダとファイル

説明

src/components

React のフィルタ、ヘッダー、レストランの詳細、レビューのコンポーネント

src/lib

React や Next.js に必ずしもバインドされていないユーティリティ関数

src/lib/firebase

Firebase 固有のコードと Firebase の構成

public

ウェブアプリの静的アセット(アイコンなど)

src/app

Next.js App Router を使用したルーティング

src/app/restaurant

API ルートハンドラ

package.jsonpackage-lock.json

npm によるプロジェクトの依存関係

next.config.js

Next.js 固有の構成(サーバー アクションが有効

jsconfig.json

JavaScript 言語サービスの設定

サーバー コンポーネントとクライアント コンポーネント

アプリが、App Router を使用する Next.js ウェブアプリである。サーバー レンダリングはアプリ全体で使用されます。たとえば、src/app/page.js ファイルはメインページを管理するサーバー コンポーネントです。src/components/RestaurantListings.jsx ファイルは、ファイルの先頭にある "use client" ディレクティブで示されるクライアント コンポーネントです。

インポート ステートメント

次のようなインポート ステートメントがあります。

import RatingPicker from "@/src/components/RatingPicker.jsx";

アプリは @ 記号を使用して相対インポート パスの煩雑さを回避します。これはパス エイリアスによって可能になります。

Firebase 固有の API

すべての Firebase API コードは src/lib/firebase ディレクトリにラップされています。個々の React コンポーネントは、Firebase 関数を直接インポートするのではなく、ラップされた関数を src/lib/firebase ディレクトリからインポートします。

モックデータ

レストランの模擬データとレビューデータは src/lib/randomData.js ファイルに含まれています。このファイルのデータは、src/lib/fakeRestaurants.js ファイル内のコードにまとめられます。

5. Firebase Hosting エミュレータを使用してローカル ホスティングを設定する

このセクションでは、Firebase Hosting エミュレータを使用して Next.js ウェブアプリをローカルで実行します。

このセクションが終わる頃には、Firebase Hosting エミュレータが Next.js アプリを実行するため、エミュレータとは別のプロセスで Next.js を実行する必要はありません。

Firebase サービス アカウントをダウンロードして使用する

この Codelab で作成するウェブアプリでは、Next.js によるサーバー側レンダリングを使用します。

Node.js 用の Firebase Admin SDK は、サーバー側のコードからセキュリティ ルールが機能するようにするために使用されます。Firebase Admin で API を使用するには、Firebase コンソールから Firebase サービス アカウントをダウンロードして使用する必要があります。

  1. Firebase コンソールで、[プロジェクトの設定] の [サービス アカウント] ページに移動します。
  2. [新しい秘密鍵を生成] > [鍵を生成] をクリックします。
  3. ファイルがファイルシステムにダウンロードされたら、そのファイルのフルパスを取得します。
    たとえば、ファイルを Downloads ディレクトリにダウンロードした場合、フルパスは次のようになります。/Users/me/Downloads/my-project-id-firebase-adminsdk-123.json
  4. ターミナルで、GOOGLE_APPLICATION_CREDENTIALS 環境変数をダウンロードした秘密鍵のパスに設定します。Unix 環境の場合、コマンドは次のようになります。
    export GOOGLE_APPLICATION_CREDENTIALS="/Users/me/Downloads/my-project-id-firebase-adminsdk-123.json"
    
  5. 新しいターミナル セッションを開始すると環境変数が失われる可能性があるため、このターミナルは開いたままにして、この Codelab の残りの部分で使用します。
    新しいターミナル セッションを開く場合は、前のコマンドを再実行する必要があります。

Firebase 構成をウェブアプリのコードに追加する

  1. Firebase コンソールで プロジェクトの設定に移動します。
  2. [SDK setup and configuration] ペインで、firebaseConfig 変数を見つけて、そのプロパティとその値をコピーします。
  3. コードエディタで .env ファイルを開き、環境変数の値に Firebase コンソールの構成値を入力します。
  4. ファイルで、既存のプロパティをコピーしたプロパティに置き換えます。
  5. ファイルを保存します。

Firebase プロジェクトでウェブアプリを初期化する

ウェブアプリを Firebase プロジェクトに接続する手順は次のとおりです。

  1. ターミナルで、Firebase でウェブ フレームワークが有効になっていることを確認します。
    firebase experiments:enable webframeworks
    
  2. Firebase を初期化します。
    firebase init
    
  3. 次のオプションを選択します。
    • Firestore: Firestore のセキュリティ ルールとインデックス ファイルの構成
    • Hosting: Firebase Hosting のファイルを構成し、必要に応じて GitHub Action のデプロイを設定する
    • Storage: Cloud Storage のセキュリティ ルール ファイルを構成する
    • エミュレータ: Firebase プロダクト用のローカル エミュレータを設定する
  4. [Use an existing project] を選択し、以前にメモしたプロジェクト ID を入力します。
  5. どのリージョンでサーバーサイド コンテンツ(該当する場合)をホストしますか?」という質問が表示されるまで、後続のすべての質問でデフォルト値を選択します。ターミナルに、現在のディレクトリに既存の Next.js コードベースが検出されたことを伝えるメッセージが表示されます。
  6. どのリージョンでサーバーサイド コンテンツをホストしますか?(該当する場合)」という質問で、Firestore と Cloud Storage 用に以前に選択したロケーションを選択します。
  7. どの Firebase エミュレータを設定しますか?」という質問が表示されるまで、後続のすべての質問でデフォルト値を選択します。この質問では、[Functions エミュレータ] と [Hosting エミュレータ] を選択します。
  8. その他の質問はすべてデフォルト値を選択してください。

セキュリティ ルールのデプロイ

このコードには、Firestore と Cloud Storage for Firebase のセキュリティ ルールのセットがすでに含まれています。セキュリティ ルールをデプロイすると、データベースとバケットのデータの不正使用に対する保護が強化されます。

  1. これらのセキュリティ ルールをデプロイするには、ターミナルで次のコマンドを実行します。
    firebase deploy --only firestore:rules,storage
    
  2. "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?"」というメッセージが表示されたら、[はい] を選択します。

Hosting エミュレータを起動する

  1. ターミナルで Hosting エミュレータを起動します。
    firebase emulators:start --only hosting
    
    ターミナルは、Hosting エミュレータがあるポート(http://localhost:5000/ など)を返します。

Hosting エミュレータの準備ができたことを示すターミナル

  1. ブラウザで、Firebase Hosting エミュレータを使用して URL に移動します。
  2. ウェブページで "Error: Firebase session cookie has incorrect..." のようなエラーが見つかった場合は、localhost 環境ですべての Cookie を削除する必要があります。手順については、Cookie を削除する | DevTools のドキュメントをご覧ください。

Cookie セッション エラー

DevTools での Cookie の削除

これで、最初のウェブアプリが表示されます。 localhost の URL でウェブアプリを表示している場合でも、コンソールで構成した実際の Firebase サービスが使用されます。

6. ウェブアプリに認証を追加する

このセクションでは、ウェブアプリにログインできるように認証を追加します。

ログイン機能とログアウト機能を実装する

  1. src/lib/firebase/auth.js ファイルで、onAuthStateChangedsignInWithGooglesignOut の各関数を次のコードに置き換えます。
export function onAuthStateChanged(cb) {
        return _onAuthStateChanged(auth, cb);
}

export async function signInWithGoogle() {
        const provider = new GoogleAuthProvider();

        try {
                await signInWithPopup(auth, provider);
        } catch (error) {
                console.error("Error signing in with Google", error);
        }
}

export async function signOut() {
        try {
                return auth.signOut();
        } catch (error) {
                console.error("Error signing out with Google", error);
        }
}

このコードでは、次の Firebase API を使用します。

Firebase API

説明

GoogleAuthProvider

Google 認証プロバイダのインスタンスを作成します。

signInWithPopup

ダイアログベースの認証フローを開始します。

auth.signOut

ユーザーをログアウトします。

src/components/Header.jsx ファイルでは、コードはすでに signInWithGoogle 関数と signOut 関数を呼び出しています。

  1. ウェブアプリでページを更新し、[Sign in with Google] をクリックします。ウェブアプリは更新されないため、ログインが成功したかどうかは不明です。

認証の変更通知を受け取る

認証の変更に登録する手順は次のとおりです。

  1. src/components/Header.jsx ファイルに移動する
  2. 関数全体を次の useUserSession に置き換えます。
function useUserSession(initialUser) {
        // The initialUser comes from the server through a server component
        const [user, setUser] = useState(initialUser);
        const router = useRouter();

        useEffect(() => {
                const unsubscribe = onAuthStateChanged(authUser => {
                        setUser(authUser);
                });
                return () => {
                        unsubscribe();
                };
        }, []);

        useEffect(() => {
                onAuthStateChanged(authUser => {
                        if (user === undefined) return;
                        if (user?.email !== authUser?.email) {
                                router.refresh();
                        }
                });
        }, [user]);

        return user;
}

このコードは、onAuthStateChanged 関数が認証状態の変更を指定したときに、React の state フックを使用してユーザーを更新します。

変更を確認する

src/app/layout.js ファイルのルート レイアウトは、ヘッダーをレンダリングし、可能な場合はユーザーをプロパティとして渡します。

<Header initialUser={currentUser?.toJSON()} />

つまり、<Header> コンポーネントは、データがある場合、サーバーの実行中にユーザーデータを表示します。最初のページ読み込み後のページのライフサイクルで認証の更新がある場合は、onAuthStateChanged ハンドラがそれらを処理します。

次に、ウェブアプリをテストして、ビルドの内容を確認します。

新しい認証動作を確認する手順は次のとおりです。

  1. ブラウザでウェブアプリを更新します。表示名がヘッダーに表示されます。
  2. ログアウトしてもう一度ログインしてください。 ページはリアルタイムで更新されます。ページを更新することはありません。別のユーザーにこの手順を繰り返すことができます。
  3. 省略可: ウェブアプリを右クリックして [ページのソースを表示] を選択し、表示名を検索します。サーバーから返される生の HTML ソースに含まれます。

7. レストラン情報を表示する

このウェブアプリには、レストランやレビューのモックデータが含まれています。

レストランを 1 つ以上追加してください

レストランの疑似データをローカルの Cloud Firestore データベースに挿入する手順は次のとおりです。

  1. ウェブアプリで、2cf67d488d8e6332.png > [サンプル レストランを追加] を選択します。
  2. Firebase コンソールの [Firestore Database] ページで、[レストラン] を選択します。レストラン コレクションには、それぞれ 1 つのレストランを表す最上位のドキュメントがあります。
  3. 数件のドキュメントをクリックして、レストランのドキュメントのプロパティを調べます。

レストランのリストを表示する

これで、Cloud Firestore データベースに Next.js ウェブアプリで表示できるレストランが追加されました。

データ取得コードを定義する手順は次のとおりです。

  1. src/app/page.js ファイルで <Home /> サーバー コンポーネントを探し、getRestaurants 関数の呼び出しを確認します。この関数は、サーバーの実行時にレストランのリストを取得します。getRestaurants 関数は次の手順で実装します。
  2. src/lib/firebase/firestore.js ファイルで、applyQueryFilters 関数と getRestaurants 関数を次のコードに置き換えます。
function applyQueryFilters(q, { category, city, price, sort }) {
        if (category) {
                q = query(q, where("category", "==", category));
        }
        if (city) {
                q = query(q, where("city", "==", city));
        }
        if (price) {
                q = query(q, where("price", "==", price.length));
        }
        if (sort === "Rating" || !sort) {
                q = query(q, orderBy("avgRating", "desc"));
        } else if (sort === "Review") {
                q = query(q, orderBy("numRatings", "desc"));
        }
        return q;
}

export async function getRestaurants(filters = {}) {
        let q = query(collection(db, "restaurants"));

        q = applyQueryFilters(q, filters);
        const results = await getDocs(q);
        return results.docs.map(doc => {
                return {
                        id: doc.id,
                        ...doc.data(),
                        // Only plain objects can be passed to Client Components from Server Components
                        timestamp: doc.data().timestamp.toDate(),
                };
        });
}
  1. ウェブアプリを更新します。レストランの画像は、ページにタイルとして表示されます。

レストランのリスティングがサーバー実行時に読み込まれることを確認する

Next.js フレームワークを使用する場合、データが読み込まれるタイミングがサーバー側とクライアント側のどちらであるかがはっきりしない場合があります。

レストランのリスティングがサーバーの実行時に読み込まれることを確認する手順は次のとおりです。

  1. ウェブアプリで DevTools を開き、JavaScript を無効にします

DevTools で JavaScipt を無効にする

  1. ウェブアプリを更新します。レストランのリスティングは引き続き読み込まれます。サーバー レスポンスでレストラン情報が返されます。JavaScript を有効にすると、クライアントサイドの JavaScript コードによってレストラン情報がハイドレートされます。
  2. DevTools で、JavaScript を再度有効にします

Cloud Firestore スナップショット リスナーを使用してレストランの更新をリッスンする

前のセクションでは、レストランの初期セットを src/app/page.js ファイルから読み込む方法を確認しました。src/app/page.js ファイルはサーバー コンポーネントであり、Firebase データ取得コードを含むサーバー上でレンダリングされます。

src/components/RestaurantListings.jsx ファイルはクライアント コンポーネントであり、サーバーでレンダリングされたマークアップをハイドレートするように構成できます。

サーバーでレンダリングされたマークアップをハイドレートするように src/components/RestaurantListings.jsx ファイルを設定するには、次の手順を行います。

  1. src/components/RestaurantListings.jsx ファイルで、すでに作成されている次のコードを確認します。
useEffect(() => {
        const unsubscribe = getRestaurantsSnapshot(data => {
                setRestaurants(data);
        }, filters);

        return () => {
                unsubscribe();
        };
}, [filters]);

このコードは getRestaurantsSnapshot() 関数を呼び出します。これは、前のステップで実装した getRestaurants() 関数に似ています。ただし、このスナップショット関数はコールバック メカニズムを提供するため、レストランのコレクションが変更されるたびにコールバックが呼び出されます。

  1. src/lib/firebase/firestore.js ファイルで、getRestaurantsSnapshot() 関数を次のコードに置き換えます。
export function getRestaurantsSnapshot(cb, filters = {}) {
        if (typeof cb !== "function") {
                console.log("Error: The callback parameter is not a function");
                return;
        }

        let q = query(collection(db, "restaurants"));
        q = applyQueryFilters(q, filters);

        const unsubscribe = onSnapshot(q, querySnapshot => {
                const results = querySnapshot.docs.map(doc => {
                        return {
                                id: doc.id,
                                ...doc.data(),
                                // Only plain objects can be passed to Client Components from Server Components
                                timestamp: doc.data().timestamp.toDate(),
                        };
                });

                cb(results);
        });

        return unsubscribe;
}

[Firestore Database] ページで行った変更がウェブアプリにリアルタイムで反映されるようになりました。

  1. ウェブアプリで、27ca5d1e8ed8adfe.png > [サンプル レストランを追加] を選択します。スナップショット機能が正しく実装されていれば、ページを更新しなくてもレストランがリアルタイムで表示されます。

8. ウェブアプリからユーザーデータを保存する

  1. src/lib/firebase/firestore.js ファイルで、updateWithRating() 関数を次のコードに置き換えます。
const updateWithRating = async (
        transaction,
        docRef,
        newRatingDocument,
        review
) => {
        const restaurant = await transaction.get(docRef);
        const data = restaurant.data();
        const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1;
        const newSumRating = (data?.sumRating || 0) + Number(review.rating);
        const newAverage = newSumRating / newNumRatings;

        transaction.update(docRef, {
                numRatings: newNumRatings,
                sumRating: newSumRating,
                avgRating: newAverage,
        });

        transaction.set(newRatingDocument, {
                ...review,
                timestamp: Timestamp.fromDate(new Date()),
        });
};

このコードにより、新しいレビューを表す新しい Firestore ドキュメントが挿入されます。また、このコードにより、レストランを表す既存の Firestore ドキュメントを更新し、評価数と計算された平均評価の数値を更新しています。

  1. 関数全体を次の addReviewToRestaurant() に置き換えます。
export async function addReviewToRestaurant(db, restaurantId, review) {
        if (!restaurantId) {
                throw new Error("No restaurant ID was provided.");
        }

        if (!review) {
                throw new Error("A valid review has not been provided.");
        }

        try {
                const docRef = doc(collection(db, "restaurants"), restaurantId);
                const newRatingDocument = doc(
                        collection(db, `restaurants/${restaurantId}/ratings`)
                );

                await runTransaction(db, transaction =>
                        updateWithRating(transaction, docRef, newRatingDocument, review)
                );
        } catch (error) {
                console.error(
                        "There was an error adding the rating to the restaurant.",
                        error
                );
                throw error;
        }
}

Next.js サーバー アクションを実装する

Next.js サーバー アクションは、フォームデータにアクセスするための便利な API(フォーム送信ペイロードからテキスト値を取得する data.get("text") など)を提供します。

Next.js サーバー アクションを使用してレビュー フォームの送信を処理する手順は次のとおりです。

  1. src/components/ReviewDialog.jsx ファイルで、<form> 要素の action 属性を見つけます。
<form action={handleReviewFormSubmission}>

action 属性値は、次のステップで実装する関数を参照します。

  1. src/app/actions.js ファイルで、handleReviewFormSubmission() 関数を次のコードに置き換えます。
// This is a next.js server action, which is an alpha feature, so
// use with caution.
// https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
export async function handleReviewFormSubmission(data) {
        const { app } = await getAuthenticatedAppForUser();
        const db = getFirestore(app);

        await addReviewToRestaurant(db, data.get("restaurantId"), {
                text: data.get("text"),
                rating: data.get("rating"),

                // This came from a hidden form field.
                userId: data.get("userId"),
        });
}

レストランのレビューを追加する

レビュー送信のサポートを実装したので、レビューが Cloud Firestore に正しく挿入されていることを確認できます。

レビューを追加して Cloud Firestore に挿入されていることを確認するには、次の手順を行います。

  1. ウェブアプリのホームページから、レストランを選択します。
  2. レストランのページで 3e19beef78bb0d0e.png をクリックします。
  3. 評価を選択してください。
  4. クチコミを書く。
  5. [送信] をクリックします。投稿したクチコミは、クチコミの一覧の一番上に表示されます。
  6. Cloud Firestore で、[ドキュメントを追加] ペインでレビューしたレストランのドキュメントを検索し、選択します。
  7. [コレクションを開始] ペインで、[評価] を選択します。
  8. [ドキュメントの追加] ペインで、確認するドキュメントを見つけて、想定どおりに挿入されたことを確認します。

Firestore エミュレータのドキュメント

9. ウェブアプリからユーザーがアップロードしたファイルを保存する

このセクションでは、ログインしたときにレストランに関連付けられている画像を置き換えることができる機能を追加します。この画像を Firebase Storage にアップロードし、レストランを表す Cloud Firestore ドキュメント内の画像の URL を更新します。

ウェブアプリからユーザーがアップロードしたファイルを保存するには、次の手順を行います。

  1. src/components/Restaurant.jsx ファイルで、ユーザーがファイルをアップロードしたときに実行されるコードを確認します。
async function handleRestaurantImage(target) {
        const image = target.files ? target.files[0] : null;
        if (!image) {
                return;
        }

        const imageURL = await updateRestaurantImage(id, image);
        setRestaurant({ ...restaurant, photo: imageURL });
}

変更は必要ありませんが、updateRestaurantImage() 関数の動作は次の手順で実装します。

  1. src/lib/firebase/storage.js ファイルで、updateRestaurantImage() 関数と uploadImage() 関数を次のコードに置き換えます。
export async function updateRestaurantImage(restaurantId, image) {
        try {
                if (!restaurantId)
                        throw new Error("No restaurant ID has been provided.");

                if (!image || !image.name)
                        throw new Error("A valid image has not been provided.");

                const publicImageUrl = await uploadImage(restaurantId, image);
                await updateRestaurantImageReference(restaurantId, publicImageUrl);

                return publicImageUrl;
        } catch (error) {
                console.error("Error processing request:", error);
        }
}

async function uploadImage(restaurantId, image) {
        const filePath = `images/${restaurantId}/${image.name}`;
        const newImageRef = ref(storage, filePath);
        await uploadBytesResumable(newImageRef, image);

        return await getDownloadURL(newImageRef);
}

updateRestaurantImageReference() 関数はすでに実装されています。この関数は、Cloud Firestore にある既存のレストランのドキュメントを更新し、更新された画像の URL を指定します。

画像アップロード機能を確認する

画像が想定どおりにアップロードされることを確認する手順は次のとおりです。

  1. ウェブアプリで、ログインしていることを確認し、レストランを選択します。
  2. 7067eb41fea41ff0.png をクリックして、ファイル システムから画像をアップロードします。イメージはローカル環境を離れ、Cloud Storage にアップロードされます。アップロードした画像はすぐに表示されます。
  3. Cloud Storage for Firebase に移動します。
  4. レストランを表すフォルダに移動します。アップロードした画像がフォルダ内に存在します。

6cf3f9e2303c931c.png

10. まとめ

これで完了です。Firebase を使用して Next.js アプリに機能を追加する方法を学びました。具体的には、次のものを使用しました。

詳細