1. 准备工作
在此 Codelab 中,您将了解如何将 Firebase 与名为 Friends Eats 的 Next.js Web 应用相集成,该应用是一个餐厅评价网站。
完成后的 Web 应用提供了一些实用功能,展示了 Firebase 如何帮助您构建 Next.js 应用。这些功能包括:
- 使用 Google 账号登录和退出功能:完成后的 Web 应用支持您使用 Google 账号登录和退出账号。用户登录和保留功能完全通过 Firebase Authentication 进行管理。
- 图片:完成后的 Web 应用可让登录用户上传餐馆图片。图片素材资源存储在 Cloud Storage for Firebase 中。Firebase JavaScript SDK 提供了已上传图片的公开网址。然后,此公开网址将存储在 Cloud Firestore 内的相关餐馆文档中。
- 评价:完成后的 Web 应用支持登录用户发布对餐厅的评价,其中包含星级评分和基于文本的消息。评价信息存储在 Cloud Firestore 中。
- 过滤条件:完成后的 Web 应用可让登录用户根据类别、位置和价格过滤餐馆列表。您还可以自定义所使用的排序方法。系统会从 Cloud Firestore 访问数据,并根据所使用的过滤条件应用 Firestore 查询。
前提条件
- 了解 Next.js 和 JavaScript 相关知识
学习内容
- 如何将 Firebase 与 Next.js 应用路由器和服务器端渲染搭配使用。
- 如何在 Cloud Storage for Firebase 中保留图片。
- 如何在 Cloud Firestore 数据库中读取和写入数据。
- 如何将“使用 Google 账号登录”用于 Firebase JavaScript SDK。
所需条件
- Git
- Java 开发套件
- 最新的稳定版 Node.js
- 您所选的浏览器(例如 Google Chrome)
- 包含代码编辑器和终端的开发环境
- 一个用于创建和管理 Firebase 项目的 Google 账号
- 能够将 Firebase 项目升级为 Blaze 定价方案
2. 设置您的开发环境
此 Codelab 提供了应用的起始代码库,并依赖于 Firebase CLI。
下载仓库
- 在您的终端中,克隆此 Codelab 的 GitHub 代码库:
git clone https://github.com/firebase/friendlyeats-web.git
- GitHub 代码库包含适用于多个平台的示例项目。不过,此 Codelab 仅使用
nextjs-start
目录。请注意以下目录:nextjs-start
:包含您在构建时所依据的起始代码。nextjs-end
:包含完成后的 Web 应用的解决方案代码。
- 在您的终端中,转到
nextjs-start
目录并安装必要的依赖项:cd friendlyeats-web/nextjs-start npm install
安装或更新 Firebase CLI
运行以下命令,验证是否已安装 Firebase CLI 且版本为 12.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
- 运行以下命令以登录 Firebase CLI:
firebase login
- 根据您是否希望 Firebase 收集数据,请输入
Y
或N
。 - 在浏览器中,选择您的 Google 账号,然后点击允许。
3. 设置您的 Firebase 项目
在本部分中,您将设置一个 Firebase 项目,并向其关联一个 Firebase Web 应用。您还将设置示例 Web 应用使用的 Firebase 服务。
创建 Firebase 项目
- 在 Firebase 控制台中,点击创建项目。
- 在输入您的项目名称文本框中,输入
FriendlyEats Codelab
(或您所选的项目名称),然后点击继续。 - 对于此 Codelab,您不需要使用 Google Analytics(分析),因此请关闭为此项目启用 Google Analytics(分析)选项。
- 点击 Create project。
- 等待您的项目完成预配,然后点击继续。
- 在 Firebase 项目中,前往项目设置。请记下您的项目 ID,因为稍后需要用到。此唯一标识符用于识别项目(例如在 Firebase CLI 中)。
向 Firebase 项目添加 Web 应用
- 前往 Firebase 项目中的项目概览页面,然后点击 Web。
- 在应用别名文本框中,输入一个容易记住的应用别名,例如
My Next.js app
。 - 选中还为此应用设置 Firebase Hosting 复选框。
- 点击注册应用 > 下一步 > 下一步 > 继续前往控制台。
升级您的 Firebase 定价方案
如需使用 Web 框架,您的 Firebase 项目必须采用 Blaze 定价方案,这意味着该项目与一个 Cloud Billing 账号相关联。
- Cloud Billing 账号要求提供付款方式,例如信用卡。
- 如果您刚开始接触 Firebase 和 Google Cloud,请确认您是否有资格获得 $300 赠金和免费试用 Cloud Billing 账号。
但请注意,完成此 Codelab 应该不会产生任何实际费用。
如需将项目升级到 Blaze 方案,请按以下步骤操作:
- 在 Firebase 控制台中,选择升级您的方案。
- 在对话框中,选择 Blaze 方案,然后按照屏幕上的说明将您的项目与 Cloud Billing 账号相关联。
如果您需要创建 Cloud Billing 账号,则可能需要返回 Firebase 控制台中的升级流程以完成升级。
在 Firebase 控制台中设置 Firebase 服务
设置身份验证
- 在 Firebase 控制台中,前往身份验证。
- 点击开始使用。
- 在其他提供商列中,点击 Google > 启用。
- 在项目的公开名称文本框中,输入一个容易记住的名称,例如
My Next.js app
。 - 从项目的支持电子邮件地址下拉列表中,选择您的电子邮件地址。
- 点击保存。
设置 Cloud Firestore
- 在 Firebase 控制台中,前往 Firestore。
- 点击创建数据库 > 以测试模式开始 > 下一步。
在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。 - 使用默认位置或选择您所选的位置。
对于真实应用,您需要选择靠近用户的位置。请注意,此位置以后无法更改,并且它也会自动成为您的默认 Cloud Storage 存储桶的位置(下一步)。 - 点击完成。
设置 Cloud Storage for Firebase
- 在 Firebase 控制台中,前往 Storage。
- 点击开始使用 > 以测试模式开始 > 下一步。
在本 Codelab 的后面,您将添加安全规则来保护您的数据。在未为您的存储桶添加安全规则的情况下,请不要公开分发或公开应用。 - 您的存储桶的位置应该已经处于选中状态(由于在上一步中设置了 Firestore)。
- 点击完成。
4. 查看起始代码库
在本部分中,您将复习应用起始代码库中的几个部分,以便在此 Codelab 中向这些部分添加功能。
文件夹和文件结构
下表简要介绍了应用的文件夹和文件结构:
文件夹和文件 | 说明 |
| 适用于过滤条件、标题、餐馆详细信息和评价的 React 组件 |
| 不一定绑定到 React 或 Next.js 的实用函数 |
| Firebase 专属代码和 Firebase 配置 |
| Web 应用中的静态资源,例如图标 |
| 使用 Next.js 应用路由器进行路由 |
| API 路由处理程序 |
| 使用 npm 的项目依赖项 |
| Next.js 专属配置(已启用服务器操作) |
| JavaScript 语言服务配置 |
服务器和客户端组件
该应用是使用应用路由器的 Next.js Web 应用。整个应用都会使用服务器渲染。例如,src/app/page.js
文件是负责主页面的服务器组件。src/components/RestaurantListings.jsx
文件是一个客户端组件,由文件开头的 "use client"
指令表示。
Import 语句
您可能会注意到如下 import 语句:
import RatingPicker from "@/src/components/RatingPicker.jsx";
应用使用 @
符号来避免复杂的相对导入路径,这是通过路径别名实现的。
Firebase 专属 API
所有 Firebase API 代码都封装在 src/lib/firebase
目录中。然后,各个 React 组件会从 src/lib/firebase
目录导入封装的函数,而不是直接导入 Firebase Functions 函数。
模拟数据
模拟餐厅和评价数据包含在 src/lib/randomData.js
文件中。该文件中的数据会汇编在 src/lib/fakeRestaurants.js
文件的代码中。
5. 使用 Firebase Hosting 模拟器设置本地托管
在本部分中,您将使用 Firebase Hosting 模拟器在本地运行 Next.js Web 应用。
完成本部分后,Firebase Hosting 模拟器会为您运行 Next.js 应用,因此您无需在模拟器的单独进程中运行 Next.js。
下载并使用 Firebase 服务账号
您将在此 Codelab 中构建的 Web 应用通过 Next.js 使用服务器端渲染。
我们使用 Node.js 版 Firebase Admin SDK 确保安全规则通过服务器端代码正常运行。如需在 Firebase Admin 中使用 API,您需要从 Firebase 控制台下载并使用 Firebase 服务账号。
- 在 Firebase 控制台中,前往项目设置中的服务账号页面。
- 点击生成新的私钥 > 生成密钥。
- 将文件下载到文件系统后,获取该文件的完整路径。
例如,如果您将文件下载到“下载”目录,则完整路径可能如下所示:/Users/me/Downloads/my-project-id-firebase-adminsdk-123.json
- 在终端中,将
GOOGLE_APPLICATION_CREDENTIALS
环境变量设置为您下载私钥的路径。在 Unix 环境中,该命令可能如下所示:export GOOGLE_APPLICATION_CREDENTIALS="/Users/me/Downloads/my-project-id-firebase-adminsdk-123.json"
- 请将此终端保持打开状态,并在本 Codelab 的剩余部分中使用它,因为如果启动新的终端会话,环境变量可能会丢失。
如果您打开一个新的终端会话,则必须重新运行上一条命令。
将 Firebase 配置添加到 Web 应用代码中
- 在 Firebase 控制台中,前往项目设置。
- 在 SDK 设置和配置窗格中,找到
firebaseConfig
变量,然后复制其属性和值。 - 在代码编辑器中打开
.env
文件,然后使用 Firebase 控制台中的配置值填充环境变量值。 - 在文件中,将现有属性替换为您复制的属性。
- 保存文件。
使用 Firebase 项目初始化 Web 应用
如需将该 Web 应用关联到您的 Firebase 项目,请按以下步骤操作:
- 在您的终端中,确保在 Firebase 中启用了 Web 框架:
firebase experiments:enable webframeworks
- 初始化 Firebase:
firebase init
- 选择以下选项:
- Firestore:为 Firestore 配置安全规则和索引文件
- Hosting:为 Firebase Hosting 配置文件,并(可选)设置 GitHub 操作部署
- 存储:为 Cloud Storage 配置安全规则文件
- 模拟器:为 Firebase 产品设置本地模拟器
- 选择使用现有项目,然后输入您之前记下的项目 ID。
- 对于所有后续问题,都选择默认值,直到出现您希望在哪个区域托管服务器端内容(如适用)?这一问题。终端会显示一条消息,提示它在当前目录中检测到现有的 Next.js 代码库。
- 对于您希望在哪个区域托管服务器端内容(如适用?)这一问题,请选择您之前为 Firestore 和 Cloud Storage 选择的位置。
- 对于所有后续问题,请选择默认值,直到出现您想要设置哪些 Firebase 模拟器?这一问题。对于此问题,请选择 Functions 模拟器和 Hosting 模拟器。
- 请为所有其他问题选择默认值。
部署安全规则
该代码已经为 Firestore 和 Cloud Storage for Firebase 设置了一组安全规则。部署安全规则后,数据库和存储桶中的数据可以得到更好的保护,避免遭到滥用。
- 如需部署这些安全规则,请在终端中运行以下命令:
firebase deploy --only firestore:rules,storage
- 如果系统询问您:
"Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?"
,请选择是。
启动 Hosting 模拟器
- 在终端中,启动 Hosting 模拟器:
firebase emulators:start --only hosting
终端会返回包含 Hosting 模拟器的端口进行响应,例如 http://localhost:5000/。
- 在浏览器中,使用 Firebase Hosting 模拟器导航到该网址。
- 如果您在网页中看到像
"Error: Firebase session cookie has incorrect..."
这样开头的错误,则需要删除 localhost 环境中的所有 Cookie。为此,请按照删除 Cookie | 开发者工具文档中的说明执行操作。
现在,您可以看到初始 Web 应用!即使您通过 localhost 网址查看 Web 应用,该应用仍会使用您在控制台中配置的实际 Firebase 服务。
6. 向 Web 应用添加身份验证功能
在本部分中,您将向 Web 应用添加身份验证,以便登录该应用。
实现登录和退出功能
- 在
src/lib/firebase/auth.js
文件中,将onAuthStateChanged
、signInWithGoogle
和signOut
函数替换为以下代码:
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 | 说明 |
创建 Google 身份验证提供方实例。 | |
启动基于对话框的身份验证流程。 | |
退出用户账号。 |
在 src/components/Header.jsx
文件中,代码已经调用了 signInWithGoogle
和 signOut
函数。
- 在 Web 应用中,刷新页面,然后点击使用 Google 账号登录。该 Web 应用无法更新,因此不确定登录是否成功。
订阅身份验证更改
如需订阅身份验证更改,请按以下步骤操作:
- 导航到
src/components/Header.jsx
文件。 - 将整个
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
处理程序会处理这些更新。
接下来,您可以测试该 Web 应用并验证您构建的内容。
如需验证新的身份验证行为,请按以下步骤操作:
- 在浏览器中,刷新 Web 应用。您的显示名称会显示在标头中。
- 退出并重新登录。该网页会实时更新,且无需刷新网页。您可以针对不同用户重复此步骤。
- 可选:右键点击该 Web 应用,选择查看网页源代码,然后搜索显示名称。该名称会在从服务器返回的原始 HTML 源代码中显示。
7. 查看餐厅信息
该 Web 应用包含餐馆和评价的模拟数据。
添加一家或多家餐馆
如需将模拟餐厅数据插入本地 Cloud Firestore 数据库,请按以下步骤操作:
- 在 Web 应用中,选择 > 添加示例餐馆。
- 在 Firebase 控制台的 Firestore 数据库页面上,选择餐厅。您会在餐馆集合中看到顶级文档,其中每个文档都代表一家餐馆。
- 点击几个文档即可浏览餐馆文档的属性。
显示餐馆列表
您的 Cloud Firestore 数据库现在包含 Next.js Web 应用可以显示的餐馆。
如需定义数据提取代码,请按以下步骤操作:
- 在
src/app/page.js
文件中,找到<Home />
服务器组件,并查看对getRestaurants
函数的调用,该函数用于在服务器运行时检索餐馆列表。您将在以下步骤中实现getRestaurants
函数。 - 在
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(),
};
});
}
- 刷新 Web 应用。餐馆图片以图块的形式显示在页面上。
验证餐馆商家信息是否在服务器运行时加载
使用 Next.js 框架时,在服务器运行时或客户端运行时加载数据的时间可能不明显。
如需验证餐厅商家信息是否在服务器运行时加载,请按以下步骤操作:
- 在 Web 应用中,打开开发者工具并停用 JavaScript。
- 刷新 Web 应用。餐馆商家信息仍会加载。服务器响应中会返回餐馆信息。当 JavaScript 处于启用状态时,餐厅信息会通过客户端 JavaScript 代码进行水合处理。
- 在开发者工具中,重新启用 JavaScript。
使用 Cloud Firestore 快照监听器监听餐馆更新
在上一部分中,您了解了如何从 src/app/page.js
文件加载初始餐馆集。src/app/page.js
文件是一个服务器组件,并在服务器上呈现,包括 Firebase 数据提取代码。
src/components/RestaurantListings.jsx
文件是一个客户端组件,可配置为水合服务器渲染的标记。
如需配置 src/components/RestaurantListings.jsx
文件以水合服务器渲染的标记,请按以下步骤操作:
- 在
src/components/RestaurantListings.jsx
文件中,观察已经为您编写的以下代码:
useEffect(() => {
const unsubscribe = getRestaurantsSnapshot(data => {
setRestaurants(data);
}, filters);
return () => {
unsubscribe();
};
}, [filters]);
此代码会调用 getRestaurantsSnapshot()
函数,类似于您在上一步中实现的 getRestaurants()
函数。不过,此快照函数提供了一种回调机制,以便在每次餐馆的集合发生更改时都会调用该回调。
- 在
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 数据库页面所做的更改现在会实时反映在 Web 应用中。
- 在 Web 应用中,选择 > 添加示例餐馆。如果正确实现快照功能,餐馆会实时显示,而无需刷新网页。
8. 保存 Web 应用中的用户数据
- 在
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 文档,使其包含评分数量和平均计算评分的更新后数据。
- 将整个
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 服务器操作处理评价表单提交,请按以下步骤操作:
- 在
src/components/ReviewDialog.jsx
文件中,找到<form>
元素中的action
属性。
<form action={handleReviewFormSubmission}>
action
属性值是指您将在下一步中实现的函数。
- 在
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,请按以下步骤操作:
- 在 Web 应用中,从首页选择一家餐馆。
- 在餐馆页面上,点击 。
- 请选择星级评分。
- 撰写评价。
- 点击提交。您的评价会显示在评价列表的顶部。
- 在 Cloud Firestore 中,在添加文档窗格中搜索您评价过的餐馆的文档并将其选中。
- 在启动集合窗格中,选择评分。
- 在添加文档窗格中,找到您的评价的文档,验证其是否已按预期插入。
9. 保存用户从 Web 应用上传的文件
在本部分中,您将添加一项功能,以便在登录时替换与餐馆关联的图片。您将图片上传到 Firebase Storage,并更新 Cloud Firestore 文档中代表该餐馆的图片网址。
如需保存用户通过该 Web 应用上传的文件,请按以下步骤操作:
- 在
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()
函数的行为。
- 在
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 中现有的餐馆文档。
验证图片上传功能
如需验证图片是否按预期上传,请按以下步骤操作:
- 在 Web 应用中,验证您已登录,并选择一家餐馆。
- 点击 ,然后从文件系统中上传图片。您的映像将离开本地环境并上传到 Cloud Storage。图片会在上传后立即显示。
- 前往 Cloud Storage for Firebase。
- 前往代表该餐馆的文件夹。您上传的图片已在文件夹中。
10. 总结
恭喜!您了解了如何使用 Firebase 为 Next.js 应用添加特性和功能。具体来说,您使用了以下各项:
- Firebase Authentication,用于启用登录和退出功能。
- Cloud Firestore,用于处理餐厅数据和餐厅评价数据。
- Cloud Storage for Firebase,用于存储餐馆图片。