1. Before you begin
In this codelab, you'll learn how to integrate Firebase with a Next.js web app called Friendly Eats, which is a website for restaurant reviews.
The completed web app offers useful features that demonstrate how Firebase can help you build Next.js apps. These features include the following:
- Automatic build and deploy: This codelab uses Firebase App Hosting to automatically build and deploy your Next.js code every time you push to a configured branch.
- Sign-in and sign-out: The completed web app lets you sign in with Google and sign out. User login and persistence is managed entirely through Firebase Authentication.
- Images: The completed web app lets signed-in users upload restaurant images. Image assets are stored in Cloud Storage for Firebase. The Firebase JavaScript SDK provides a public URL to uploaded images. This public URL is then stored in the relevant restaurant document in Cloud Firestore.
- Reviews: The completed web app lets signed-in users post reviews of restaurants that consist of a star rating and a text-based message. Review information is stored in Cloud Firestore.
- Filters: The completed web app lets signed-in users filter the list of restaurants based on category, location, and price. You can also customize the sorting method used. Data is accessed from Cloud Firestore, and Firestore queries are applied based on the filters used.
Prerequisites
- A GitHub account
- Knowledge of Next.js and JavaScript
What you'll learn
- How to use Firebase with the Next.js App Router and server-side rendering.
- How to persist images in Cloud Storage for Firebase.
- How to read and write data in a Cloud Firestore database.
- How to use sign-in with Google with the Firebase JavaScript SDK.
What you'll need
- Git
- A recent stable version of Node.js
- A browser of your choice, such as Google Chrome
- A development environment with a code editor and terminal
- A Google account for the creation and management of your Firebase project
- The ability to upgrade your Firebase project to the Blaze pricing plan
2. Set up your development environment and GitHub repository
This codelab provides the app's starter codebase and relies on the Firebase CLI.
Create a GitHub repository
The codelab source can be found at https://github.com/firebase/friendlyeats-web. The repository contains sample projects for multiple platforms. However, this codelab uses only the nextjs-start
directory. Take note of the following directories:
* `nextjs-start`: contains the starter code upon which you build.
* `nextjs-end`: contains the solution code for the finished web app.
Copy the nextjs-start
folder into your own repository:
- Using a terminal, create a new folder on your computer and change into the new directory:
mkdir codelab-friendlyeats-web cd codelab-friendlyeats-web
- Use the giget npm package to fetch only the
nextjs-start
folder:npx giget@latest gh:firebase/friendlyeats-web/nextjs-start#master . --install
- Track changes locally with git:
git init git commit -a -m "codelab starting point" git branch -M main
- Create a new GitHub repository: https://github.com/new. Name it anything you'd like.
- Copy the new URL that GitHub creates for you. It will look like one of the following:
https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git
orgit@github.com:<USER_NAME>/<REPOSITORY_NAME>.git
- Push local changes to your new GitHub repository by running the following command. Substitute your actual repository URL for the
<REPOSITORY_URL>
placeholder.git remote add origin <REPOSITORY_URL> git push -u origin main
- You should now see the starter code in your GitHub repository.
Install or update the Firebase CLI
Run the following command to verify that you have the Firebase CLI installed and that it's v13.9.0 or higher:
firebase --version
If you see a lower version or you don't have the Firebase CLI installed, run the install command:
npm install -g firebase-tools@latest
If you're unable to install the Firebase CLI because of permission errors, see the npm documentation or use another installation option.
Log in to Firebase
- Run the following command to log in to the Firebase CLI:
firebase login
- Depending on whether you want Firebase to collect data, enter
Y
orN
. - In your browser, select your Google account, and then click Allow.
3. Set up your Firebase project
In this section, you'll set up a Firebase project and associate a Firebase web app with it. You'll also set up the Firebase services used by the sample web app.
Create a Firebase project
- In the Firebase console, click Add project.
- In the Enter your project name text box, enter
FriendlyEats Codelab
(or a project name of your choice), and then click Continue. - In the Confirm Firebase billing plan modal, confirm the plan is Blaze, and then click Confirm plan
- For this codelab, you don't need Google Analytics, so toggle off the Enable Google Analytics for this project option.
- Click Create project.
- Wait for your project to provision, and then click Continue.
- In your Firebase project, go to Project Settings. Note your project ID because you need it later. This unique identifier is how your project is identified (for example, in the Firebase CLI).
Upgrade your Firebase pricing plan
To use Firebase App Hosting and Cloud Storage for Firebase, your Firebase project needs to be on the pay-as-you go (Blaze) pricing plan, which means it's linked to a Cloud Billing account.
- A Cloud Billing account requires a payment method, like a credit card.
- If you're new to Firebase and Google Cloud, check if you're eligible for a $300 credit and a Free Trial Cloud Billing account.
- If you're doing this codelab as part of an event, ask your organizer if there are any Cloud credits available.
To upgrade your project to the Blaze plan, follow these steps:
- In the Firebase console, select to upgrade your plan.
- Select the Blaze plan. Follow the on-screen instructions to link a Cloud Billing account to your project.
If you needed to create a Cloud Billing account as part of this upgrade, you might need to navigate back to the upgrade flow in the Firebase console to complete the upgrade.
Add a web app to your Firebase project
- Navigate to your Project overview in your Firebase project, and then click Web.
If you already have apps registered in your project, click Add app to see the Web icon. - In the App nickname text box, enter a memorable app nickname, such as
My Next.js app
. - Keep the Also set up Firebase Hosting for this app checkbox unchecked.
- Click Register app > Next > Next > Continue to console.
Set up Firebase services in the Firebase console
Set up Authentication
- In the Firebase console, navigate to Authentication.
- Click Get started.
- In the Additional providers column, click Google > Enable.
- In the Public-facing name for project text box, enter a memorable name, such as
My Next.js app
. - From the Support email for project drop-down, select your email address.
- Click Save.
Set up Cloud Firestore
- In the left-panel of the Firebase console, expand Build and then select Firestore database.
- Click Create database.
- Leave the Database ID set to
(default)
. - Select a location for your database, then click Next.
For a real app, you want to choose a location that's close to your users. - Click Start in test mode. Read the disclaimer about the security rules.
Later in this codelab, you'll add Security Rules to secure your data. Do not distribute or expose an app publicly without adding Security Rules for your database. - Click Create.
Set up Cloud Storage for Firebase
- In the left-panel of the Firebase console, expand Build and then select Storage.
- Click Get started.
- Select a location for your default Storage bucket.
Buckets inUS-WEST1
,US-CENTRAL1
, andUS-EAST1
can take advantage of the "Always Free" tier for Google Cloud Storage. Buckets in all other locations follow Google Cloud Storage pricing and usage. - Click Start in test mode. Read the disclaimer about the security rules.
Later in this codelab, you'll add security rules to secure your data. Do not distribute or expose an app publicly without adding Security Rules for your Storage bucket. - Click Create.
4. Review the starter codebase
In this section, you'll review a few areas of the app's starter codebase to which you'll add functionality in this codelab.
Folder and file structure
The following table contains an overview of the folder and file structure of the app:
Folders and files | Description |
| React components for filters, headers, restaurant details, and reviews |
| Utility functions that aren't necessarily bound to React or Next.js |
| Firebase-specific code and Firebase configuration |
| Static assets in the web app, like icons |
| Routing with the Next.js App Router |
| An API route handler |
| Project dependencies with npm |
| Next.js-specific configuration (server actions are enabled) |
| JavaScript language-service configuration |
Server and client components
The app is a Next.js web app that uses the App Router. Server rendering is used throughout the app. For example, the src/app/page.js
file is a server component responsible for the main page. The src/components/RestaurantListings.jsx
file is a client component denoted by the "use client"
directive at the beginning of the file.
Import statements
You might notice import statements like the following:
import RatingPicker from "@/src/components/RatingPicker.jsx";
The app uses the @
symbol to avoid clunky relative import paths and is made possible by path aliases.
Firebase-specific APIs
All Firebase API code is wrapped in the src/lib/firebase
directory. Individual React components then import the wrapped functions from the src/lib/firebase
directory, rather than importing Firebase functions directly.
Mock data
Mock restaurant and review data is contained in the src/lib/randomData.js
file. Data from that file is assembled in the code in the src/lib/fakeRestaurants.js
file.
5. Create an App Hosting backend
In this section, you'll set up an App Hosting backend to watch a branch on your git repository.
By the end of this section, you'll have an App Hosting backend connected to your repository in GitHub that will automatically re-build and roll out a new version of your app whenever you push a new commit to your main
branch.
Deploy Security Rules
The code already has sets of security rules for Firestore and for Cloud Storage for Firebase. After you deploy the Security Rules, the data in your database and your bucket are better protected from misuse.
- In your terminal, configure the CLI to use the Firebase project you created earlier:
When prompted for an alias, enterfirebase use --add
friendlyeats-codelab
. - To deploy these Security Rules, run this command in your terminal:
firebase deploy --only firestore:rules,storage
- If you're asked:
"Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?"
, pressEnter
to select Yes.
Add your Firebase configuration to your web app code
- In the Firebase console, navigate to your Project settings.
- Scroll down to the Your Apps section, and then select the Firebase Web App that you created earlier in this codelab.
- Copy the
firebaseConfig
variable, and copy its properties and their values. - Open the
apphosting.yaml
file in your code editor, and fill in the environment variable values with the configuration values from the Firebase console. - In the file, replace the existing properties with those that you copied.
- Save the file.
Create a backend
- Navigate to the App Hosting page in the Firebase console:
- Click "Get started" to start the backend creation flow. Configure your backend as follows:
- Follow the prompts in the first step to connect the GitHub repository you created earlier.
- Set deployment settings:
- Keep the root directory as
/
- Set the live branch to
main
- Enable automatic rollouts
- Keep the root directory as
- Name your backend
friendlyeats-codelab
. - In "Create or associate a Firebase web app", pick the web app you configured earlier from the "Select an existing Firebase web app" drop down.
- Click "Finish and deploy". After a moment, you'll be taken to a new page where you can see the status of your new App Hosting backend!
- Once your rollout completes, click your free domain under "domains". This may take a few minutes to begin working due to DNS propagation.
You've deployed the initial web app! Every time you push a new commit to the main
branch of your GitHub repository, you'll see a new build and rollout begin in the Firebase console, and your site will automatically update once the rollout completes.
6. Add authentication to the web app
In this section, you add authentication to the web app so that you can log in to it.
Implement the sign-in and sign-out functions
- In the
src/lib/firebase/auth.js
file, replace theonAuthStateChanged
,signInWithGoogle
, andsignOut
functions with the following code:
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);
}
}
This code uses the following Firebase APIs:
Firebase API | Description |
Creates a Google authentication provider instance. | |
Starts a dialog-based authentication flow. | |
Signs out the user. |
In the src/components/Header.jsx
file, the code already invokes the signInWithGoogle
and signOut
functions.
- Create a commit with commit message "Adding Google Authentication" and push it to your GitHub repository. 1. Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- In the web app, refresh the page and click Sign in with Google. The web app doesn't update, so it's unclear whether sign-in succeeded.
Send authentication state to the server
In order to pass authentication state to the server, we'll use a service worker. Replace the fetchWithFirebaseHeaders
and getAuthIdToken
functions with the following code:
async function fetchWithFirebaseHeaders(request) {
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const installations = getInstallations(app);
const headers = new Headers(request.headers);
const [authIdToken, installationToken] = await Promise.all([
getAuthIdToken(auth),
getToken(installations),
]);
headers.append("Firebase-Instance-ID-Token", installationToken);
if (authIdToken) headers.append("Authorization", `Bearer ${authIdToken}`);
const newRequest = new Request(request, { headers });
return await fetch(newRequest);
}
async function getAuthIdToken(auth) {
await auth.authStateReady();
if (!auth.currentUser) return;
return await getIdToken(auth.currentUser);
}
Read authentication state on the server
We'll use FirebaseServerApp to mirror the client's authentication state on the server.
Open src/lib/firebase/serverApp.js
, and replace the getAuthenticatedAppForUser
function:
export async function getAuthenticatedAppForUser() {
const idToken = headers().get("Authorization")?.split("Bearer ")[1];
console.log('firebaseConfig', JSON.stringify(firebaseConfig));
const firebaseServerApp = initializeServerApp(
firebaseConfig,
idToken
? {
authIdToken: idToken,
}
: {}
);
const auth = getAuth(firebaseServerApp);
await auth.authStateReady();
return { firebaseServerApp, currentUser: auth.currentUser };
}
Subscribe to authentication changes
To subscribe to authentication changes, follow these steps:
- Navigate to the
src/components/Header.jsx
file. - Replace the
useUserSession
function with the following code:
function useUserSession(initialUser) {
// The initialUser comes from the server via a server component
const [user, setUser] = useState(initialUser);
const router = useRouter();
// Register the service worker that sends auth state back to server
// The service worker is built with npm run build-service-worker
useEffect(() => {
if ("serviceWorker" in navigator) {
const serializedFirebaseConfig = encodeURIComponent(JSON.stringify(firebaseConfig));
const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`
navigator.serviceWorker
.register(serviceWorkerUrl)
.then((registration) => console.log("scope is: ", registration.scope));
}
}, []);
useEffect(() => {
const unsubscribe = onAuthStateChanged((authUser) => {
setUser(authUser)
})
return () => unsubscribe()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
onAuthStateChanged((authUser) => {
if (user === undefined) return
// refresh when user changed to ease testing
if (user?.email !== authUser?.email) {
router.refresh()
}
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user])
return user;
}
This code uses a React state hook to update the user when the onAuthStateChanged
function specifies that there's a change to the authentication state.
Verify changes
The root layout in the src/app/layout.js
file renders the header and passes in the user, if available, as a prop.
<Header initialUser={currentUser?.toJSON()} />
This means that the <Header>
component renders user data, if available, during server run time. If there are any authentication updates during the page lifecycle after initial page load, the onAuthStateChanged
handler handles them.
Now it's time to roll out a new build and verify what you built.
- Create a commit with commit message "Show signin state" and push it to your GitHub repository.
- Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- Verify the new authentication behavior:
- In your browser, refresh the web app. Your display name appears in the header.
- Sign out and sign in again. The page updates in real-time without a page refresh. You can repeat this step with different users.
- Optional: Right-click the web app, select View page source, and search for the display name. It appears in the raw HTML source returned from the server.
7. View restaurant information
The web app includes mock data for restaurants and reviews.
Add one or more restaurants
To insert mock restaurant data into your local Cloud Firestore database, follow these steps:
- In the web app, select > Add sample restaurants.
- In the Firebase console on the Firestore Database page, select restaurants. You see the top-level documents in the restaurant collection, each of which represents a restaurant.
- Click a few documents to explore the properties of a restaurant document.
Display the list of restaurants
Your Cloud Firestore database now has restaurants that the Next.js web app can display.
To define the data-fetching code, follow these steps:
- In the
src/app/page.js
file, find the<Home />
server component, and review the call to thegetRestaurants
function, which retrieves a list of restaurants at server run time. You implement thegetRestaurants
function in the following steps. - In the
src/lib/firebase/firestore.js
file, replace theapplyQueryFilters
andgetRestaurants
functions with the following code:
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(db = db, 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(),
};
});
}
- Create a commit with commit message "Read the list of restaurants from Firestore" and push it to your GitHub repository.
- Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- In the web app, refresh the page. Restaurant images appear as tiles on the page.
Verify that the restaurant listings load at server run time
Using the Next.js framework, it might not be obvious when data is loaded at server run time or client-side run time.
To verify that restaurant listings load at server run time, follow these steps:
- In the web app, open DevTools and disable JavaScript.
- Refresh the web app. The restaurant listings still load. Restaurant information is returned in the server response. When JavaScript is enabled, the restaurant information is hydrated through the client-side JavaScript code.
- In DevTools, re-enable JavaScript.
Listen for restaurant updates with Cloud Firestore snapshot listeners
In the previous section, you saw how the initial set of restaurants loaded from the src/app/page.js
file. The src/app/page.js
file is a server component and is rendered on the server, including the Firebase data-fetching code.
The src/components/RestaurantListings.jsx
file is a client component and can be configured to hydrate server-rendered markup.
To configure the src/components/RestaurantListings.jsx
file to hydrate server-rendered markup, follow these steps:
- In the
src/components/RestaurantListings.jsx
file, observe the following code, which is already written for you:
useEffect(() => {
const unsubscribe = getRestaurantsSnapshot(data => {
setRestaurants(data);
}, filters);
return () => {
unsubscribe();
};
}, [filters]);
This code invokes the getRestaurantsSnapshot()
function, which is similar to the getRestaurants()
function that you implemented in a previous step. However this snapshot function provides a callback mechanism so that the callback is invoked every time a change is made to the restaurant's collection.
- In the
src/lib/firebase/firestore.js
file, replace thegetRestaurantsSnapshot()
function with the following code:
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;
}
Changes made through the Firestore Database page now reflect in the web app in real time.
- Create a commit with commit message "Listen for realtime restaurant updates" and push it to your GitHub repository.
- Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- In the web app, select > Add sample restaurants. If your snapshot function is implemented correctly, the restaurants appear in real-time without a page refresh.
8. Save user-submitted reviews from the web app
- In the
src/lib/firebase/firestore.js
file, replace theupdateWithRating()
function with the following code:
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()),
});
};
This code inserts a new Firestore document representing the new review. The code also updates the existing Firestore document that represents the restaurant with updated figures for the number of ratings and the average calculated rating.
- Replace the
addReviewToRestaurant()
function with the following code:
export async function addReviewToRestaurant(db, restaurantId, review) {
if (!restaurantId) {
throw new Error("No restaurant ID has been 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`)
);
// corrected line
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;
}
}
Implement a Next.js Server Action
A Next.js Server Action provides a convenient API to access form data, such as data.get("text")
to get the text value from the form submission payload.
To use a Next.js Server Action to process the review form submission, follow these steps:
- In the
src/components/ReviewDialog.jsx
file, find theaction
attribute in the<form>
element.
<form action={handleReviewFormSubmission}>
The action
attribute value refers to a function that you implement in the next step.
- In the
src/app/actions.js
file, replace thehandleReviewFormSubmission()
function with the following code:
// 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"),
});
}
Add reviews for a restaurant
You implemented support for review submissions, so now you can verify that your reviews are inserted into Cloud Firestore correctly.
To add a review and verify that it's inserted into Cloud Firestore, follow these steps:
- Create a commit with commit message "Allow users to submit restaurant reviews" and push it to your GitHub repository.
- Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- Refresh the web app, and select a restaurant from the home page.
- On the restaurant's page, click .
- Select a star rating.
- Write a review.
- Click Submit. Your review appears at the top of the list of reviews.
- In Cloud Firestore, search the Add document pane for the document of the restaurant that you reviewed and select it.
- In the Start collection pane, select ratings.
- In the Add document pane, find the document for your review to verify that it was inserted as expected.
9. Save user-uploaded files from the web app
In this section, you add functionality so that you can replace the image associated with a restaurant when you're logged in. You upload the image to Firebase Storage, and update the image URL in the Cloud Firestore document that represents the restaurant.
To save user-uploaded files from the web app, follow these steps:
- In the
src/components/Restaurant.jsx
file, observe the code that runs when the user uploads a file:
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 });
}
No changes are needed, but you implement the behavior of the updateRestaurantImage()
function in the following steps.
- In the
src/lib/firebase/storage.js
file, replace theupdateRestaurantImage()
anduploadImage()
functions with the following code:
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);
}
The updateRestaurantImageReference()
function is already implemented for you. This function updates an existing restaurant document in Cloud Firestore with an updated image URL.
Verify the image-upload functionality
To verify that the image uploads as expected, follow these steps:
- Create a commit with commit message "Allow users to change each restaurants' photo" and push it to your GitHub repository.
- Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- In the web app, verify that you're logged in and select a restaurant.
- Click and upload an image from your filesystem. Your image leaves your local environment and is uploaded to Cloud Storage. The image appears immediately after you upload it.
- Navigate to Cloud Storage for Firebase.
- Navigate to the folder that represents the restaurant. The image that you uploaded exists in the folder.
10. Summarize restaurant reviews with generative ai
In this section, you'll add a review summary feature so that a user can quickly understand what everyone thinks of a restaurant without having to read every review.
Store a Gemini API key in Cloud Secret Manager
- To use the Gemini API, you'll need an API key. Create a key in Google AI Studio.
- App Hosting integrates with Cloud Secret Manager to allow you to store sensitive values like API keys securely:
- In a terminal, run the command to create a new secret:
firebase apphosting:secrets:set gemini-api-key
- When prompted for the secret value, copy and paste your Gemini API key from Google AI Studio.
- When asked if the new secret should be added to
apphosting.yaml
, enterY
to accept.
Your Gemini API key is now stored securely in Cloud Secret manager, and is accessible to your App Hosting backend.
Implement the review summary component
- In
src/components/Reviews/ReviewSummary.jsx
, replace theGeminiSummary
function with the following code:export async function GeminiSummary({ restaurantId }) { const { firebaseServerApp } = await getAuthenticatedAppForUser(); const reviews = await getReviewsByRestaurantId( getFirestore(firebaseServerApp), restaurantId ); const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); const model = genAI.getGenerativeModel({ model: "gemini-pro"}); const reviewSeparator = "@"; const prompt = ` Based on the following restaurant reviews, where each review is separated by a '${reviewSeparator}' character, create a one-sentence summary of what people think of the restaurant. Here are the reviews: ${reviews.map(review => review.text).join(reviewSeparator)} `; try { const result = await model.generateContent(prompt); const response = await result.response; const text = response.text(); return ( <div className="restaurant__review_summary"> <p>{text}</p> <p>✨ Summarized with Gemini</p> </div> ); } catch (e) { console.error(e); return <p>Error contacting Gemini</p>; } }
- Create a commit with commit message "Use AI to summarize reviews" and push it to your GitHub repository.
- Open the App Hosting page in the Firebase console and wait for your new rollout to complete.
- Open a page for a restaurant. At the top, you should see a one-sentence summary of all the reviews on the page.
- Add a new review and refresh the page. You should see the summary change.
11. Conclusion
Congratulations! You learned how to use Firebase to add features and functionality to a Next.js app. Specifically, you used the following:
- Firebase App Hosting to automatically build and deploy your Next.js code every time you push to a configured branch.
- Firebase Authentication to enable sign-in and sign-out functionality.
- Cloud Firestore for restaurant data and restaurant review data.
- Cloud Storage for Firebase for restaurant images.