Tích hợp Firebase với ứng dụng Next.js

1. Trước khi bắt đầu

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách tích hợp Firebase với một ứng dụng web Next.js có tên là Friendly Eats. Đây là một trang web để đánh giá nhà hàng.

Ứng dụng web Friendly Eats

Ứng dụng web hoàn chỉnh cung cấp các tính năng hữu ích minh hoạ cách Firebase có thể giúp bạn xây dựng ứng dụng Next.js. Các tính năng này bao gồm:

  • Tự động xây dựng và triển khai: Lớp học lập trình này sử dụng tính năng Lưu trữ ứng dụng Firebase để tự động tạo và triển khai mã Next.js mỗi khi bạn đẩy đến một nhánh được định cấu hình.
  • Đăng nhập và đăng xuất: Ứng dụng web hoàn chỉnh cho phép bạn đăng nhập bằng Google và đăng xuất. Hoạt động đăng nhập và khả năng lưu trữ của người dùng được quản lý hoàn toàn thông qua tính năng Xác thực Firebase.
  • Hình ảnh: Ứng dụng web hoàn chỉnh cho phép người dùng đã đăng nhập tải hình ảnh nhà hàng lên. Thành phần hình ảnh được lưu trữ trong Cloud Storage cho Firebase. SDK JavaScript của Firebase cung cấp URL công khai cho các hình ảnh được tải lên. Sau đó, URL công khai này được lưu trữ trong tài liệu về nhà hàng có liên quan trong Cloud Firestore.
  • Bài đánh giá: Ứng dụng web đã hoàn thiện cho phép người dùng đã đăng nhập đăng bài đánh giá về các nhà hàng đi kèm điểm xếp hạng theo sao và thông báo bằng văn bản. Thông tin về bài đánh giá được lưu trữ trong Cloud Firestore.
  • Bộ lọc: Ứng dụng web đã hoàn thiện cho phép người dùng đã đăng nhập lọc danh sách nhà hàng dựa trên danh mục, vị trí và giá cả. Bạn cũng có thể tuỳ chỉnh phương thức sắp xếp được sử dụng. Dữ liệu được truy cập từ Cloud Firestore và các truy vấn trên Firestore được áp dụng dựa trên các bộ lọc đã sử dụng.

Điều kiện tiên quyết

  • Tài khoản GitHub
  • Có kiến thức về Next.js và JavaScript

Kiến thức bạn sẽ học được

  • Cách sử dụng Firebase với Trình định tuyến ứng dụng Next.js và tính năng hiển thị phía máy chủ.
  • Cách lưu trữ hình ảnh trong Cloud Storage cho Firebase.
  • Cách đọc và ghi dữ liệu trong cơ sở dữ liệu Cloud Firestore.
  • Cách sử dụng tính năng đăng nhập bằng Google với SDK JavaScript của Firebase.

Bạn cần có

  • Git
  • Phiên bản ổn định gần đây của Node.js
  • Một trình duyệt mà bạn chọn, chẳng hạn như Google Chrome
  • Môi trường phát triển có trình soạn thảo mã và dòng lệnh
  • Tài khoản Google để tạo và quản lý dự án Firebase
  • Khả năng nâng cấp dự án Firebase của bạn lên Gói giá linh hoạt

2. Thiết lập môi trường phát triển và kho lưu trữ GitHub

Lớp học lập trình này cung cấp cơ sở mã khởi đầu của ứng dụng và dựa trên Giao diện dòng lệnh (CLI) của Firebase.

Tạo kho lưu trữ GitHub

Bạn có thể tìm thấy nguồn của lớp học lập trình này tại https://github.com/firebase/friendlyeats-web. Kho lưu trữ này chứa các dự án mẫu cho nhiều nền tảng. Tuy nhiên, lớp học lập trình này chỉ sử dụng thư mục nextjs-start. Hãy lưu ý các thư mục sau:

* `nextjs-start`: contains the starter code upon which you build.
* `nextjs-end`: contains the solution code for the finished web app.

Sao chép thư mục nextjs-start vào kho lưu trữ của riêng bạn:

  1. Sử dụng cửa sổ dòng lệnh, tạo một thư mục mới trên máy tính và thay đổi sang thư mục mới:
    mkdir codelab-friendlyeats-web
    
    cd codelab-friendlyeats-web
    
  2. Sử dụng gói npm giget để chỉ tìm nạp thư mục nextjs-start:
    npx giget@latest gh:firebase/friendlyeats-web/nextjs-start#master . --install
    
  3. Theo dõi các thay đổi cục bộ bằng git:
    git init
    
    git commit -a -m "codelab starting point"
    
    git branch -M main
    
  4. Tạo kho lưu trữ GitHub mới: https://github.com/new. Bạn có thể đặt tên bất kỳ cho lớp này.
    1. GitHub sẽ cung cấp cho bạn một URL kho lưu trữ mới có dạng https://github.com//.git hoặc git@github.com:/.git. Sao chép URL này.
  5. Đẩy các thay đổi cục bộ vào kho lưu trữ GitHub mới. Chạy lệnh sau, thay thế URL kho lưu trữ của bạn cho phần giữ chỗ .
    git remote add origin <your-repository-url>
    
    git push -u origin main
    
  6. Bây giờ, bạn sẽ thấy mã khởi động trong kho lưu trữ GitHub.

Cài đặt hoặc cập nhật Giao diện dòng lệnh (CLI) của Firebase

Chạy lệnh sau để xác minh rằng bạn đã cài đặt Firebase CLI và phiên bản là 13.9.0 trở lên:

firebase --version

Nếu bạn thấy phiên bản thấp hơn hoặc chưa cài đặt Firebase CLI, hãy chạy lệnh cài đặt:

npm install -g firebase-tools@latest

Nếu bạn không thể cài đặt Giao diện dòng lệnh (CLI) của Firebase do lỗi quyền, hãy xem tài liệu về npm hoặc sử dụng một tuỳ chọn cài đặt khác.

Đăng nhập vào Firebase

  1. Chạy lệnh sau để đăng nhập vào Firebase CLI:
    firebase login
    
  2. Tuỳ thuộc vào việc bạn muốn Firebase thu thập dữ liệu, hãy nhập Y hoặc N.
  3. Trong trình duyệt, hãy chọn Tài khoản Google của bạn rồi nhấp vào Cho phép.

3. Thiết lập dự án Firebase

Trong phần này, bạn sẽ thiết lập một dự án Firebase và liên kết một ứng dụng web Firebase với dự án đó. Bạn cũng sẽ thiết lập các dịch vụ Firebase mà ứng dụng web mẫu sử dụng.

Tạo dự án Firebase

  1. Trong bảng điều khiển của Firebase, hãy nhấp vào Thêm dự án.
  2. Trong hộp văn bản Enter your project name (Nhập tên dự án), hãy nhập FriendlyEats Codelab (hoặc tên dự án mà bạn chọn), sau đó nhấp vào Continue (Tiếp tục).
  3. Trong cửa sổ Xác nhận kế hoạch thanh toán Firebase, hãy xác nhận kế hoạch là Blaze, sau đó nhấp vào Xác nhận gói
  4. Đối với lớp học lập trình này, bạn không cần Google Analytics, vì vậy, hãy tắt tuỳ chọn Bật Google Analytics cho dự án này.
  5. Nhấp vào Tạo dự án.
  6. Chờ dự án được cấp phép, rồi nhấp vào Tiếp tục.
  7. Trong dự án Firebase của bạn, hãy chuyển đến phần Cài đặt dự án. Ghi lại mã dự án vì bạn sẽ cần đến mã này vào lúc khác. Giá trị nhận dạng duy nhất này là cách xác định dự án của bạn (ví dụ: trong Firebase CLI).

Nâng cấp gói giá Firebase

Để sử dụng tính năng Lưu trữ ứng dụng Firebase và Cloud Storage cho Firebase, dự án Firebase của bạn cần phải sử dụng gói trả tiền theo mức dùng (Blaze), có nghĩa là dự án được liên kết với Tài khoản thanh toán trên đám mây.

  • Để sử dụng tài khoản thanh toán Cloud, bạn phải có một phương thức thanh toán, chẳng hạn như thẻ tín dụng.
  • Nếu bạn mới sử dụng Firebase và Google Cloud, hãy kiểm tra xem bạn có đủ điều kiện nhận khoản tín dụng 300 USD và tài khoản thanh toán Cloud dùng thử miễn phí hay không.
  • Nếu bạn tham gia lớp học lập trình này trong một sự kiện, hãy hỏi người tổ chức xem có tín dụng Cloud nào không.

Để nâng cấp dự án của bạn lên Gói linh hoạt, hãy làm theo các bước sau:

  1. Trong bảng điều khiển của Firebase, hãy chọn nâng cấp gói.
  2. Chọn gói Blaze. Làm theo hướng dẫn trên màn hình để liên kết tài khoản thanh toán Cloud với dự án của bạn.
    Nếu cần tạo một tài khoản thanh toán Cloud trong quá trình nâng cấp này, bạn có thể phải quay lại quy trình nâng cấp trong bảng điều khiển của Firebase để hoàn tất quá trình nâng cấp.

Thêm ứng dụng web vào dự án Firebase

  1. Chuyển đến phần Tổng quan về dự án trong dự án Firebase, sau đó nhấp vào e41f2efdd9539c31.png. Web.

    Nếu bạn đã đăng ký ứng dụng trong dự án, hãy nhấp vào Thêm ứng dụng để xem biểu tượng Web.
  2. Trong hộp văn bản Biệt hiệu ứng dụng, nhập biệt hiệu dễ nhớ của ứng dụng, chẳng hạn như My Next.js app.
  3. Bỏ đánh dấu hộp Cũng thiết lập tính năng Lưu trữ Firebase cho ứng dụng này.
  4. Nhấp vào Đăng ký ứng dụng > Tiếp theo > Tiếp theo > Tiếp tục đến bảng điều khiển.

Thiết lập các dịch vụ Firebase trong bảng điều khiển Firebase

Thiết lập tính năng Xác thực

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến phần Xác thực.
  2. Nhấp vào Bắt đầu.
  3. Trong cột Nhà cung cấp bổ sung, hãy nhấp vào Google > Bật.
  4. Trong hộp văn bản Tên hiển thị công khai của dự án, hãy nhập một tên dễ nhớ, chẳng hạn như My Next.js app.
  5. Trong trình đơn thả xuống Email hỗ trợ cho dự án, hãy chọn địa chỉ email của bạn.
  6. Nhấp vào Lưu.

Thiết lập Cloud Firestore

  1. Trong bảng điều khiển bên trái của bảng điều khiển của Firebase, hãy mở rộng mục Build (Tạo) rồi chọn Firestore cơ sở dữ liệu.
  2. Nhấp vào Tạo cơ sở dữ liệu.
  3. Đặt Mã nhận dạng cơ sở dữ liệu thành (default).
  4. Chọn vị trí cho cơ sở dữ liệu của bạn, sau đó nhấp vào Tiếp theo.
    Đối với ứng dụng thực tế, bạn muốn chọn vị trí gần người dùng của mình.
  5. Nhấp vào Bắt đầu ở chế độ thử nghiệm. Đọc tuyên bố từ chối trách nhiệm về các quy tắc bảo mật.
    Trong phần sau của lớp học lập trình này, bạn sẽ thêm Quy tắc bảo mật để bảo mật dữ liệu của mình. Không phân phối hoặc tiết lộ ứng dụng công khai khi chưa thêm Quy tắc bảo mật cho cơ sở dữ liệu của bạn.
  6. Nhấp vào Tạo.

Thiết lập Cloud Storage cho Firebase

  1. Trong bảng điều khiển bên trái của bảng điều khiển của Firebase, hãy mở rộng mục Build (Tạo) rồi chọn Storage (Bộ nhớ).
  2. Nhấp vào Bắt đầu.
  3. Chọn vị trí cho bộ chứa Bộ nhớ mặc định.
    Các bộ chứa trong US-WEST1, US-CENTRAL1US-EAST1 có thể tận dụng cấp "Luôn miễn phí" cho Google Cloud Storage. Các bộ chứa ở tất cả các vị trí khác tuân theo mức giá và mức sử dụng của Google Cloud Storage.
  4. Nhấp vào Bắt đầu ở chế độ thử nghiệm. Đọc tuyên bố từ chối trách nhiệm về các quy tắc bảo mật.
    Trong phần sau của lớp học lập trình này, bạn sẽ thêm các quy tắc bảo mật để bảo mật dữ liệu của mình. Không không phân phối hoặc tiết lộ ứng dụng công khai khi chưa thêm Quy tắc bảo mật cho bộ chứa Storage.
  5. Nhấp vào Tạo.

4. Xem lại cơ sở mã khởi đầu

Trong phần này, bạn sẽ ôn tập một số khía cạnh trong cơ sở mã khởi đầu của ứng dụng mà bạn sẽ thêm chức năng vào trong lớp học lập trình này.

Cấu trúc thư mục và tệp

Bảng sau đây trình bày thông tin tổng quan về cấu trúc thư mục và tệp của ứng dụng:

Thư mục và tệp

Nội dung mô tả

src/components

Các thành phần React cho bộ lọc, tiêu đề, thông tin chi tiết về nhà hàng và bài đánh giá

src/lib

Các hàm tiện ích không nhất thiết phải liên kết với React hoặc Next.js

src/lib/firebase

Mã dành riêng cho Firebase và cấu hình Firebase

public

Thành phần tĩnh trong ứng dụng web, chẳng hạn như biểu tượng

src/app

Định tuyến bằng Trình định tuyến ứng dụng Next.js

src/app/restaurant

Một trình xử lý định tuyến API

package.jsonpackage-lock.json

Phần phụ thuộc dự án với npm

next.config.js

Cấu hình dành riêng cho Next.js (hành động của máy chủ đang bật)

jsconfig.json

Cấu hình dịch vụ ngôn ngữ JavaScript

Thành phần máy chủ và máy khách

Ứng dụng này là một ứng dụng web Next.js sử dụng Bộ định tuyến ứng dụng. Tính năng kết xuất máy chủ được sử dụng trong toàn bộ ứng dụng. Ví dụ: tệp src/app/page.js là một thành phần máy chủ chịu trách nhiệm về trang chính. Tệp src/components/RestaurantListings.jsx là một thành phần ứng dụng được biểu thị bằng lệnh "use client" ở đầu tệp.

Câu lệnh nhập

Bạn có thể thấy các câu lệnh nhập như sau:

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

Ứng dụng sử dụng biểu tượng @ để tránh các đường dẫn nhập tương đối rối mắt và có thể thực hiện được bằng các bí danh đường dẫn.

API dành riêng cho Firebase

Tất cả mã Firebase API đều được gói trong thư mục src/lib/firebase. Sau đó, các thành phần React riêng lẻ sẽ nhập các hàm được gói từ thư mục src/lib/firebase, thay vì nhập trực tiếp các hàm Firebase.

Dữ liệu mô phỏng

Dữ liệu đánh giá và nhà hàng mô phỏng nằm trong tệp src/lib/randomData.js. Dữ liệu từ tệp đó được tập hợp trong mã trong tệp src/lib/fakeRestaurants.js.

5. Tạo phần phụ trợ của dịch vụ Lưu trữ ứng dụng

Trong phần này, bạn sẽ thiết lập phần phụ trợ Lưu trữ ứng dụng để theo dõi một nhánh trên kho lưu trữ git.

Ở cuối phần này, bạn sẽ có một phần phụ trợ của tính năng Lưu trữ ứng dụng được kết nối với kho lưu trữ trong GitHub. Phần phụ trợ này sẽ tự động tạo lại và ra mắt một phiên bản mới của ứng dụng mỗi khi bạn đẩy một cam kết mới vào nhánh main.

Triển khai quy tắc bảo mật

Mã này đã có sẵn các bộ quy tắc bảo mật cho Firestore và cho Cloud Storage cho Firebase. Sau khi bạn triển khai Quy tắc bảo mật, dữ liệu trong cơ sở dữ liệu và bộ chứa của bạn sẽ được bảo vệ tốt hơn khỏi hành vi sử dụng sai mục đích.

  1. Trong dòng lệnh, hãy định cấu hình CLI để sử dụng dự án Firebase mà bạn đã tạo trước đó:
    firebase use --add
    
    Khi được nhắc nhập một email đại diện, hãy nhập friendlyeats-codelab.
  2. Để triển khai các Quy tắc bảo mật này, hãy chạy lệnh sau trong thiết bị đầu cuối của bạn:
    firebase deploy --only firestore:rules,storage
    
  3. Nếu bạn được hỏi: "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?", hãy nhấn Enter để chọn .

Thêm cấu hình Firebase vào mã ứng dụng web

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến phần Cài đặt dự án.
  2. Trong ngăn Thiết lập và cấu hình SDK, hãy nhấp vào "Thêm ứng dụng" rồi nhấp vào biểu tượng dấu ngoặc mã để đăng ký một ứng dụng web mới.
  3. Ở cuối quy trình tạo ứng dụng web, hãy sao chép biến firebaseConfig rồi sao chép các thuộc tính cũng như giá trị của biến đó.
  4. Mở tệp apphosting.yaml trong trình soạn thảo mã và điền giá trị biến môi trường bằng giá trị cấu hình từ bảng điều khiển Firebase.
  5. Trong tệp, hãy thay thế các thuộc tính hiện có bằng các thuộc tính mà bạn đã sao chép.
  6. Lưu tệp.

Tạo phần phụ trợ

  1. Chuyển đến trang Lưu trữ ứng dụng trong bảng điều khiển của Firebase:

Trạng thái ban đầu của bảng điều khiển Dịch vụ lưu trữ ứng dụng, với nút &quot;Bắt đầu&quot;

  1. Nhấp vào "Bắt đầu" để bắt đầu quy trình tạo phần phụ trợ. Định cấu hình phần phụ trợ như sau:
  2. Làm theo lời nhắc trong bước đầu tiên để kết nối kho lưu trữ GitHub mà bạn đã tạo trước đó.
  3. Thiết lập chế độ cài đặt triển khai:
    1. Giữ thư mục gốc là /
    2. Đặt nhánh trực tiếp thành main
    3. Bật chế độ phát hành tự động
  4. Đặt tên cho phần phụ trợ là friendlyeats-codelab.
  5. Trong mục "Tạo hoặc liên kết ứng dụng web Firebase", hãy chọn ứng dụng web mà bạn đã thiết lập trước đó trên trình đơn thả xuống "Chọn một ứng dụng web Firebase hiện có".
  6. Nhấp vào "Hoàn tất và triển khai". Sau một lát, bạn sẽ được chuyển đến một trang mới để xem trạng thái của phần phụ trợ mới của dịch vụ Lưu trữ ứng dụng!
  7. Sau khi quá trình triển khai hoàn tất, hãy nhấp vào miền miễn phí của bạn trong mục "miền". Quá trình này có thể mất vài phút để bắt đầu hoạt động do quá trình truyền DNS.

Bạn đã triển khai ứng dụng web ban đầu! Mỗi lần đẩy một cam kết mới vào nhánh main của kho lưu trữ GitHub, bạn sẽ thấy một bản dựng mới và bắt đầu phát hành trong bảng điều khiển của Firebase, đồng thời trang web của bạn sẽ tự động cập nhật sau khi quá trình phát hành hoàn tất.

6. Thêm phương thức xác thực vào ứng dụng web

Trong phần này, bạn sẽ thêm tính năng xác thực vào ứng dụng web để có thể đăng nhập vào ứng dụng đó.

Triển khai chức năng đăng nhập và đăng xuất

  1. Trong tệp src/lib/firebase/auth.js, hãy thay thế các hàm onAuthStateChanged, signInWithGooglesignOut bằng mã sau:
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);
  }
}

Mã này sử dụng các API Firebase sau:

API Firebase

Nội dung mô tả

GoogleAuthProvider

Tạo một thực thể của nhà cung cấp dịch vụ xác thực của Google.

signInWithPopup

Bắt đầu quy trình xác thực dựa trên hộp thoại.

auth.signOut

Đăng xuất người dùng.

Trong tệp src/components/Header.jsx, mã này đã gọi các hàm signInWithGooglesignOut.

  1. Tạo một thay đổi có thông báo thay đổi "Thêm tính năng Xác thực bằng Google" rồi đẩy thay đổi đó vào kho lưu trữ GitHub. 1. Mở trang Lưu trữ ứng dụng trong bảng điều khiển của Firebase và đợi quá trình triển khai mới hoàn tất.
  2. Trong ứng dụng web, hãy làm mới trang rồi nhấp vào Đăng nhập bằng Google. Ứng dụng web không cập nhật, vì vậy, không rõ liệu quá trình đăng nhập có thành công hay không.

Gửi trạng thái xác thực đến máy chủ

Để chuyển trạng thái xác thực tới máy chủ, chúng ta sẽ sử dụng service worker. Thay thế các hàm fetchWithFirebaseHeadersgetAuthIdToken bằng mã sau:

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);
}

Đọc trạng thái xác thực trên máy chủ

Chúng ta sẽ sử dụng FirebaseServerApp để phản ánh trạng thái xác thực của ứng dụng trên máy chủ.

Mở src/lib/firebase/serverApp.js và thay thế hàm getAuthenticatedAppForUser:

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 };
}

Đăng ký nhận các thay đổi về phương thức xác thực

Để đăng ký nhận thông báo về các thay đổi đối với quy trình xác thực, hãy làm theo các bước sau:

  1. Chuyển đến tệp src/components/Header.jsx.
  2. Thay thế hàm useUserSession bằng mã sau:
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;
}

Mã này sử dụng một trình nối trạng thái React để cập nhật người dùng khi hàm onAuthStateChanged chỉ định rằng có thay đổi đối với trạng thái xác thực.

Xác minh các thay đổi

Bố cục gốc trong tệp src/app/layout.js sẽ hiển thị tiêu đề và truyền cho người dùng (nếu có) dưới dạng một giá trị.

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

Tức là thành phần <Header> hiển thị dữ liệu người dùng (nếu có) trong thời gian chạy máy chủ. Nếu có bất kỳ nội dung cập nhật nào về xác thực trong vòng đời của trang sau khi tải trang ban đầu, trình xử lý onAuthStateChanged sẽ xử lý các nội dung đó.

Giờ đã đến lúc triển khai bản dựng mới và xác minh những gì bạn đã tạo.

  1. Tạo một cam kết có thông báo cam kết "Show signin state" (Hiện trạng thái đăng nhập) rồi đẩy thông báo đó vào kho lưu trữ GitHub của bạn.
  2. Mở trang Lưu trữ ứng dụng trong bảng điều khiển Firebase và đợi quá trình triển khai mới hoàn tất.
  3. Xác minh hành vi xác thực mới:
    1. Trong trình duyệt, hãy làm mới ứng dụng web. Tên hiển thị của bạn sẽ xuất hiện trên tiêu đề.
    2. Đăng xuất rồi đăng nhập lại. Trang sẽ cập nhật theo thời gian thực mà không cần làm mới trang. Bạn có thể lặp lại bước này với những người dùng khác.
    3. Không bắt buộc: Nhấp chuột phải vào ứng dụng web, chọn Xem nguồn trang rồi tìm tên hiển thị. Tệp này xuất hiện trong nguồn HTML thô được máy chủ trả về.

7. Xem thông tin về nhà hàng

Ứng dụng web này có dữ liệu mô phỏng về nhà hàng và bài đánh giá.

Thêm một hoặc nhiều nhà hàng

Để chèn dữ liệu nhà hàng mô phỏng vào cơ sở dữ liệu Cloud Firestore cục bộ, hãy làm theo các bước sau:

  1. Trong ứng dụng web, hãy chọn 2cf67d488d8e6332.png > Thêm nhà hàng mẫu.
  2. Trong bảng điều khiển của Firebase trên trang Firestore Database (Cơ sở dữ liệu khôi phục), hãy chọn nhà hàng. Bạn sẽ thấy các tài liệu cấp cao nhất trong tập hợp nhà hàng, mỗi tài liệu đại diện cho một nhà hàng.
  3. Nhấp vào một số tài liệu để khám phá các thuộc tính của một tài liệu về nhà hàng.

Hiển thị danh sách nhà hàng

Cơ sở dữ liệu Cloud Firestore của bạn hiện có các nhà hàng mà ứng dụng web Next.js có thể hiển thị.

Để xác định mã tìm nạp dữ liệu, hãy làm theo các bước sau:

  1. Trong tệp src/app/page.js, hãy tìm thành phần máy chủ <Home /> rồi xem lại lệnh gọi đến hàm getRestaurants. Hàm này truy xuất danh sách các nhà hàng trong thời gian chạy máy chủ. Bạn sẽ triển khai hàm getRestaurants theo các bước sau.
  2. Trong tệp src/lib/firebase/firestore.js, hãy thay thế hàm applyQueryFiltersgetRestaurants bằng đoạn mã sau:
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(),
		};
	});
}
  1. Tạo một thay đổi có thông báo thay đổi &quot;Đọc danh sách nhà hàng từ Firestore&quot; rồi đẩy thay đổi đó vào kho lưu trữ GitHub của bạn.
  2. Mở trang Lưu trữ ứng dụng trong bảng điều khiển của Firebase và đợi quá trình triển khai mới hoàn tất.
  3. Trong ứng dụng web, hãy làm mới trang. Hình ảnh nhà hàng xuất hiện dưới dạng ô trên trang.

Xác minh rằng trang thông tin nhà hàng tải trong thời gian chạy máy chủ

Khi sử dụng khung Next.js, bạn có thể không biết rõ thời điểm dữ liệu được tải tại thời gian chạy máy chủ hay thời gian chạy phía máy khách.

Để xác minh rằng trang thông tin nhà hàng tải trong thời gian chạy máy chủ, hãy làm theo các bước sau:

  1. Trong ứng dụng web, hãy mở Công cụ cho nhà phát triển rồi tắt JavaScript.

Tắt JavaScipt trong Công cụ cho nhà phát triển

  1. Làm mới ứng dụng web. Trang thông tin nhà hàng vẫn tải. Thông tin nhà hàng được trả về trong phản hồi của máy chủ. Khi JavaScript được bật, thông tin về nhà hàng sẽ được nước thông qua mã JavaScript phía máy khách.
  2. Trong Công cụ cho nhà phát triển, hãy bật lại JavaScript.

Nghe thông tin cập nhật về nhà hàng bằng trình nghe ảnh chụp nhanh Cloud Firestore

Trong phần trước, bạn đã thấy cách nhóm nhà hàng ban đầu được tải từ tệp src/app/page.js. Tệp src/app/page.js là một thành phần máy chủ và được hiển thị trên máy chủ, bao gồm cả mã tìm nạp dữ liệu Firebase.

Tệp src/components/RestaurantListings.jsx là một thành phần ứng dụng và có thể được định cấu hình để bổ sung dữ liệu đánh dấu do máy chủ kết xuất.

Để định cấu hình tệp src/components/RestaurantListings.jsx nhằm làm mới mã đánh dấu do máy chủ hiển thị, hãy làm theo các bước sau:

  1. Trong tệp src/components/RestaurantListings.jsx, hãy quan sát mã sau đây (mã này đã được viết sẵn cho bạn):
useEffect(() => {
        const unsubscribe = getRestaurantsSnapshot(data => {
                setRestaurants(data);
        }, filters);

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

Mã này sẽ gọi hàm getRestaurantsSnapshot(), tương tự như hàm getRestaurants() mà bạn đã triển khai ở bước trước. Tuy nhiên, hàm ảnh chụp nhanh này cung cấp một cơ chế gọi lại để lệnh gọi lại được gọi mỗi khi có thay đổi đối với tập hợp nhà hàng.

  1. Trong tệp src/lib/firebase/firestore.js, hãy thay thế hàm getRestaurantsSnapshot() bằng mã sau:
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;
}

Những thay đổi bạn thực hiện thông qua trang Firestore Database (Cơ sở dữ liệu Khôi phục dữ liệu) giờ đây sẽ phản ánh trong ứng dụng web theo thời gian thực.

  1. Tạo một thay đổi có thông báo thay đổi "Nghe thông tin cập nhật về nhà hàng theo thời gian thực" rồi đẩy thay đổi đó vào kho lưu trữ GitHub.
  2. Mở trang Lưu trữ ứng dụng trong bảng điều khiển Firebase và đợi quá trình triển khai mới hoàn tất.
  3. Trong ứng dụng web, hãy chọn 27ca5d1e8ed8adfe.png. > Thêm nhà hàng mẫu. Nếu hàm chụp nhanh của bạn được triển khai chính xác, các nhà hàng sẽ xuất hiện theo thời gian thực mà không cần làm mới trang.

8. Lưu bài đánh giá do người dùng gửi từ ứng dụng web

  1. Trong tệp src/lib/firebase/firestore.js, hãy thay thế hàm updateWithRating() bằng mã sau:
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()),
	});
};

Mã này chèn một tài liệu mới trên Firestore để minh hoạ cho bài đánh giá mới. Mã này cũng cập nhật tài liệu Firestore hiện có đại diện cho nhà hàng bằng các số liệu mới nhất về số lượt xếp hạng và điểm xếp hạng trung bình được tính toán.

  1. Thay thế hàm addReviewToRestaurant() bằng mã sau:
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;
	}
}

Triển khai Hành động máy chủ Next.js

Hành động trên máy chủ Next.js cung cấp một API thuận tiện để truy cập vào dữ liệu biểu mẫu, chẳng hạn như data.get("text") để nhận giá trị văn bản từ tải trọng gửi biểu mẫu.

Để sử dụng Hành động của máy chủ Next.js nhằm xử lý việc gửi biểu mẫu xem xét, hãy thực hiện theo các bước sau:

  1. Trong tệp src/components/ReviewDialog.jsx, hãy tìm thuộc tính action trong phần tử <form>.
<form action={handleReviewFormSubmission}>

Giá trị thuộc tính action đề cập đến một hàm mà bạn triển khai trong bước tiếp theo.

  1. Trong tệp src/app/actions.js, hãy thay thế hàm handleReviewFormSubmission() bằng mã sau:
// 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"),
        });
}

Thêm bài đánh giá về nhà hàng

Bạn đã triển khai tính năng hỗ trợ gửi bài đánh giá. Do đó, giờ đây, bạn có thể xác minh rằng các bài đánh giá của mình được chèn vào Cloud Firestore một cách chính xác.

Để thêm bài đánh giá và xác minh rằng bài đánh giá đó đã được chèn vào Cloud Firestore, hãy làm theo các bước sau:

  1. Tạo một lệnh xác nhận có thông báo cam kết "Cho phép người dùng gửi bài đánh giá về nhà hàng" và đẩy bản cam kết đó vào kho lưu trữ GitHub của bạn.
  2. Mở trang Lưu trữ ứng dụng trong bảng điều khiển của Firebase và đợi quá trình triển khai mới hoàn tất.
  3. Làm mới ứng dụng web rồi chọn một nhà hàng trên trang chủ.
  4. Trên trang của nhà hàng, hãy nhấp vào 3e19beef78bb0d0e.png.
  5. Chọn một điểm xếp hạng theo sao.
  6. Viết bài đánh giá.
  7. Nhấp vào Gửi. Bài đánh giá của bạn sẽ xuất hiện ở đầu danh sách bài đánh giá.
  8. Trong Cloud Firestore, hãy tìm trong ngăn Add document (Thêm tài liệu) cho tài liệu của nhà hàng mà bạn đã đánh giá và chọn tài liệu đó.
  9. Trong ngăn Bắt đầu thu thập, hãy chọn điểm xếp hạng.
  10. Trong ngăn Add document (Thêm tài liệu), hãy tìm tài liệu cần xem xét để chắc chắn rằng tài liệu đó được chèn như dự kiến.

Tài liệu trong Trình mô phỏng Firestore

9. Lưu tệp do người dùng tải lên từ ứng dụng web

Trong phần này, bạn thêm chức năng để có thể thay thế hình ảnh được liên kết với một nhà hàng khi bạn đăng nhập. Bạn tải hình ảnh lên Firebase Storage và cập nhật URL hình ảnh trong tài liệu trên Cloud Firestore thể hiện nhà hàng.

Để lưu tệp do người dùng tải lên từ ứng dụng web, hãy làm theo các bước sau:

  1. Trong tệp src/components/Restaurant.jsx, hãy quan sát mã chạy khi người dùng tải tệp lên:
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 });
}

Bạn không cần thay đổi gì, nhưng hãy triển khai hành vi của hàm updateRestaurantImage() theo các bước sau.

  1. Trong tệp src/lib/firebase/storage.js, hãy thay thế các hàm updateRestaurantImage()uploadImage() bằng mã sau:
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);
}

Hàm updateRestaurantImageReference() đã được triển khai cho bạn. Hàm này cập nhật tài liệu nhà hàng hiện có trong Cloud Firestore bằng URL hình ảnh mới cập nhật.

Xác minh chức năng tải hình ảnh lên

Để xác minh rằng hình ảnh tải lên như mong đợi, hãy làm theo các bước sau:

  1. Tạo một lệnh xác nhận có thông báo xác nhận "Cho phép người dùng thay đổi ảnh của từng nhà hàng" và đẩy ảnh đó vào kho lưu trữ GitHub của bạn.
  2. Mở trang Lưu trữ ứng dụng trong bảng điều khiển Firebase và đợi quá trình triển khai mới hoàn tất.
  3. Trong ứng dụng web, hãy xác minh rằng bạn đã đăng nhập rồi chọn một nhà hàng.
  4. Nhấp vào 7067eb41fea41ff0.pngS rồi tải hình ảnh lên từ hệ thống tệp của bạn. Hình ảnh của bạn rời khỏi môi trường cục bộ và được tải lên Cloud Storage. Hình ảnh sẽ xuất hiện ngay sau khi bạn tải lên.
  5. Chuyển đến Cloud Storage cho Firebase.
  6. Chuyển đến thư mục đại diện cho nhà hàng. Hình ảnh bạn tải lên có trong thư mục.

6cf3f9e2303c931c.png

10. Tóm tắt bài đánh giá nhà hàng bằng AI tạo sinh

Trong phần này, bạn sẽ thêm tính năng tóm tắt bài đánh giá để người dùng có thể nhanh chóng hiểu suy nghĩ của mọi người về một nhà hàng mà không cần phải đọc từng bài đánh giá.

Lưu trữ khoá Gemini API trong Cloud Secret Manager

  1. Để sử dụng Gemini API, bạn cần có khoá API. Tạo khoá trong Google AI Studio.
  2. Dịch vụ Lưu trữ ứng dụng tích hợp với Cloud Secret Manager để giúp bạn lưu trữ các giá trị nhạy cảm như khoá API một cách an toàn:
    1. Trong dòng lệnh, hãy chạy lệnh để tạo một khoá bí mật mới:
    firebase apphosting:secrets:set gemini-api-key
    
    1. Khi được nhắc nhập giá trị bí mật, hãy sao chép và dán khoá Gemini API của bạn từ Google AI Studio.
    2. Khi được hỏi có nên thêm khoá bí mật mới vào apphosting.yaml hay không, hãy nhập Y để chấp nhận.

Khoá API Gemini của bạn hiện được lưu trữ an toàn trong Trình quản lý bí mật trên đám mây và có thể truy cập được vào phần phụ trợ của dịch vụ Lưu trữ ứng dụng.

Triển khai thành phần tóm tắt bài đánh giá

  1. Trong src/components/Reviews/ReviewSummary.jsx, hãy thay thế hàm GeminiSummary bằng mã sau:
    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>;
        }
    }
    
  2. Tạo một thay đổi có thông báo thay đổi "Sử dụng AI để tóm tắt bài đánh giá" và đẩy thay đổi đó vào kho lưu trữ GitHub của bạn.
  3. Mở trang Lưu trữ ứng dụng trong bảng điều khiển Firebase và đợi quá trình triển khai mới hoàn tất.
  4. Mở trang của một nhà hàng. Ở trên cùng, bạn sẽ thấy nội dung tóm tắt gồm một câu về tất cả các bài đánh giá trên trang.
  5. Thêm một bài đánh giá mới rồi làm mới trang. Bạn sẽ thấy nội dung tóm tắt thay đổi.

11. Kết luận

Xin chúc mừng! Bạn đã tìm hiểu cách sử dụng Firebase để thêm các tính năng và chức năng vào ứng dụng Next.js. Cụ thể, bạn đã sử dụng những nội dung sau:

Tìm hiểu thêm