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 (Ăn uống thân thiện), đâ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 này cung cấp các tính năng hữu ích minh hoạ cách Firebase có thể giúp bạn tạo ứng dụng Next.js. Các tính năng này bao gồm:

  • Tự động tạo và triển khai: Lớp học lập trình này sử dụng Dịch vụ lưu trữ ứng dụng Firebase để tự động tạo và triển khai mã Next.js mỗi khi bạn đẩy mã lên một nhánh đã đị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à duy trì trạng thái đăng nhập của người dùng được quản lý hoàn toàn thông qua 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. Firebase JavaScript SDK cung cấp một URL công khai cho hình ảnh đã tải lên. Sau đó, URL công khai này sẽ được lưu trữ trong tài liệu nhà hàng có liên quan trong Cloud Firestore.
  • Bài đánh giá: Ứng dụng web hoàn chỉnh cho phép người dùng đã đăng nhập đăng bài đánh giá về nhà hàng, bao gồm điểm xếp hạng theo sao và thông báo bằng văn bản. Thông tin đánh giá được lưu trữ trong Cloud Firestore.
  • Bộ lọc: Ứng dụng web hoàn chỉnh 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á. Bạn cũng có thể tuỳ chỉnh phương thức sắp xếp được dùng. Dữ liệu được truy cập từ Cloud Firestore và các truy vấn Firestore được áp dụng dựa trên các bộ lọc được dùng.

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

  • Tài khoản GitHub
  • 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 Bộ định tuyến ứng dụng Next.js và tính năng hiển thị phía máy chủ.
  • Cách duy 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
  • Một 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 chỉnh sửa mã và cửa sổ dòng lệnh
  • Một Tài khoản Google để tạo và quản lý dự án Firebase
  • Khả năng nâng cấp dự án Firebase 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 vào Firebase CLI.

Tạo kho lưu trữ trên GitHub

Bạn có thể tìm thấy nguồn của lớp học lập trình 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 một thiết bị đầu cuối, tạo một thư mục mới trên máy tính của bạn và chuyển 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 add .
    
    git commit -m "codelab starting point"
    
    git branch -M main
    
  4. Tạo một kho lưu trữ mới trên GitHub: https://github.com/new. Bạn có thể đặt tên theo ý muốn.
  5. Sao chép URL mới mà GitHub tạo cho bạn. Mã này sẽ có dạng như một trong những mã sau:
    • https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git hoặc
    • git@github.com:<USER_NAME>/<REPOSITORY_NAME>.git
  6. Đẩy các thay đổi cục bộ vào kho lưu trữ GitHub mới bằng cách chạy lệnh sau. Thay thế phần giữ chỗ <REPOSITORY_URL> bằng URL thực tế của kho lưu trữ.
    git remote add origin <REPOSITORY_URL>
    
    git push -u origin main
    
  7. Giờ đây, bạn sẽ thấy mã khởi đầu 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 này là 14.1.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 về quyền, hãy xem tài liệu về npm hoặc sử dụng lựa 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 hay không, 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 một dự án Firebase

  1. Đăng nhập vào bảng điều khiển của Firebase bằng chính Tài khoản Google mà bạn đã dùng ở bước trước.
  2. Nhấp vào nút này để tạo một dự án mới, rồi nhập tên dự án (ví dụ: FriendlyEats Codelab).
  3. Nhấp vào Tiếp tục.
  4. Nếu được nhắc, hãy xem xét và chấp nhận các điều khoản của Firebase, rồi nhấp vào Tiếp tục.
  5. (Không bắt buộc) Bật tính năng hỗ trợ của AI trong bảng điều khiển của Firebase (còn gọi là "Gemini trong Firebase").
  6. Đố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 lựa chọn Google Analytics.
  7. Nhấp vào Tạo dự án, đợi dự án được cấp phép rồi nhấp vào Tiếp tục.

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

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

Để nâng cấp dự án lên gói Blaze, 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 một tài khoản thanh toán trê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 trên Cloud trong quá trình nâng cấp này, bạn có thể cần quay lại quy trình nâng cấp trong bảng điều khiển Firebase để hoàn tất quá trình nâng cấp.

Thêm một ứ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 của bạn, rồi nhấp vào e41f2efdd9539c31.png Web.

    Nếu bạn đã đăng ký ứng dụng trong dự án của mình, 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 của ứng dụng, hãy nhập một biệt hiệu dễ nhớ cho ứng dụng, chẳng hạn như My Next.js app.
  3. Bỏ đánh dấu hộp Đồng thời thiết lập dịch vụ Lưu trữ Firebase cho ứng dụng này.
  4. Nhấp vào Đăng ký ứng dụng > 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 của 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 mục 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 dự án công khai, 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 Firebase, hãy mở rộng mục Tạo rồi chọn Cơ sở dữ liệu Firestore.
  2. Nhấp vào Tạo cơ sở dữ liệu.
  3. Để nguyên Mã cơ sở dữ liệu được đặt thành (default).
  4. Chọn một vị trí cho cơ sở dữ liệu của bạn, rồi nhấp vào Tiếp theo.
    Đối với một ứng dụng thực tế, bạn nên chọn một vị trí gần với 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.
    Sau này trong 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 công khai một ứng dụng mà không 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 Firebase, hãy mở rộng Tạo rồi chọn Bộ nhớ.
  2. Nhấp vào Bắt đầu.
  3. Chọn một vị trí cho bộ chứa lưu trữ mặc định.
    Các bộ chứa ở US-WEST1, US-CENTRAL1US-EAST1 có thể tận dụng cấp"Luôn miễn phí" của Google Cloud Storage. Các bộ chứa ở tất cả những vị trí khác đều 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.
    Sau này trong 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 phân phối hoặc công khai ứng dụng mà không thêm Quy tắc bảo mật cho Nhóm lưu trữ.
  5. Nhấp vào Tạo.

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

Mã này đã có các quy tắc bảo mật cho Firestore và 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à nhóm 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 thiết bị đầu cuối, 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 bí danh, hãy nhập friendlyeats-codelab.
  2. Để triển khai các Quy tắc bảo mật này (cũng như các chỉ mục sẽ cần thiết sau này), hãy chạy lệnh sau trong thiết bị đầu cuối:
    firebase deploy --only firestore,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 .

4. Xem xét cơ sở mã khởi đầu

Trong phần này, bạn sẽ xem xét một số khu vực của 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 chứa 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

Tài sản tĩnh trong ứng dụng web, chẳng hạn như biểu tượng

src/app

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

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 (các thao tác trên máy chủ được bật)

jsconfig.json

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

Các 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 App Router. Tính năng kết xuất phía máy chủ được 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 khách được biểu thị bằng chỉ thị "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 này sử dụng biểu tượng @ để tránh các đường dẫn nhập tương đối rườm rà và được thực hiện nhờ 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 bao bọc 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à hàng và bài đánh giá mô phỏng có trong tệp src/lib/randomData.js. Dữ liệu từ tệp đó được tập hợp trong mã của tệp src/lib/fakeRestaurants.js.

5. Tạo một phần phụ trợ Dịch vụ lưu trữ ứng dụng

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

Đến cuối phần này, bạn sẽ có một phần phụ trợ Dịch vụ lưu trữ ứng dụng được kết nối với kho lưu trữ của bạn trong GitHub. Phần phụ trợ này sẽ tự động tạo lại và triển khai một phiên bản mới của ứng dụng bất cứ khi nào bạn đẩy một cam kết mới vào nhánh main.

Tạo một 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 0 của bảng điều khiển Lưu trữ ứng dụng, có 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. Chọn khu vực. Đối với một ứng dụng thực tế, bạn nên chọn khu vực gần người dùng nhất.
  3. Làm theo lời nhắc trong bước "Nhập kho lưu trữ GitHub" để kết nối với kho lưu trữ GitHub mà bạn đã tạo trước đó.
  4. Đặt chế độ cài đặt triển khai:
    1. Giữ thư mục gốc là /
    2. Đặt nhánh phát hành công khai thành main
    3. Bật tính năng phát hành tự động
  5. Đặt tên cho phần phụ trợ của bạn friendlyeats-codelab.
  6. Trong phần "Liên kết một ứng dụng web Firebase", hãy nhấp vào "Tạo một ứng dụng web Firebase mới".
  7. 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, nơi bạn có thể xem trạng thái của phần phụ trợ mới cho Dịch vụ lưu trữ ứng dụng!
  8. Sau khi quá trình phát hành hoàn tất, hãy nhấp vào miền miễn phí của bạn trong mục "miền". Do quá trình truyền tin DNS, có thể mất vài phút thì chế độ này mới bắt đầu hoạt động.
  9. Rất tiếc! Khi tải trang, bạn sẽ thấy thông báo lỗi "Lỗi ứng dụng: đã xảy ra một ngoại lệ phía máy chủ (xem nhật ký máy chủ để biết thêm thông tin)."
  10. Trong bảng điều khiển của Firebase, hãy kiểm tra thẻ "Nhật ký" của phần phụ trợ Lưu trữ ứng dụng. Bạn sẽ thấy nhật ký "Lỗi: chưa triển khai". Chúng ta sẽ khắc phục vấn đề đó trong bước tiếp theo khi thêm quy trình xác thực.

Bạn đã triển khai ứng dụng web ban đầu! Mỗi khi đẩ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 và quy trình phát hành mới bắt đầu trong bảng điều khiển Firebase, đồng thời trang web của bạn sẽ tự động cập nhật sau khi quy trình phát hành hoàn tất.

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

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

Thêm miền được uỷ quyền

Xác thực Firebase sẽ chỉ chấp nhận các yêu cầu đăng nhập từ những miền mà bạn cho phép. Tại đây, chúng ta sẽ thêm miền của phần phụ trợ Lưu trữ ứng dụng vào danh sách các miền được phê duyệt trong dự án của bạn.

  1. Sao chép miền phụ trợ của Dịch vụ lưu trữ ứng dụng từ trang "Tổng quan" của Dịch vụ lưu trữ ứng dụng.
  2. Chuyển đến thẻ Cài đặt uỷ quyền rồi chọn Miền được uỷ quyền.
  3. Nhấp vào nút Thêm miền.
  4. Nhập miền của phần phụ trợ Dịch vụ lưu trữ ứng dụng.
  5. Nhấp vào Thêm.

Triển khai các hàm đă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, onIdTokenChanged, signInWithGooglesignOut bằng đoạn mã sau:
export function onAuthStateChanged(cb) {
  return _onAuthStateChanged(auth, cb);
}

export function onIdTokenChanged(cb) {
  return _onIdTokenChanged(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);
  }
}

Đoạn mã này sử dụng các API Firebase sau:

Firebase API

Nội dung mô tả

auth.onAuthStateChanged

Thêm một đối tượng tiếp nhận dữ liệu để theo dõi những thay đổi về trạng thái đăng nhập của người dùng.

auth.onIdTokenChanged

Thêm một đối tượng tiếp nhận dữ liệu để theo dõi các thay đổi đối với mã thông báo nhận dạng của người dùng.

GoogleAuthProvider

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

signInWithPopup

Bắt đầu một 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.

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

Để truyền trạng thái xác thực đến máy chủ, chúng ta sẽ sử dụng cookie. Bất cứ khi nào trạng thái xác thực thay đổi trong ứng dụng, chúng tôi sẽ cập nhật cookie __session.

Trong src/components/Header.jsx, hãy thay thế hàm useUserSession bằng đoạn mã sau:

function useUserSession(initialUser) {
  useEffect(() => {
    return onIdTokenChanged(async (user) => {
      if (user) {
        const idToken = await user.getIdToken();
        await setCookie("__session", idToken);
      } else {
        await deleteCookie("__session");
      }
      if (initialUser?.uid === user?.uid) {
        return;
      }
      window.location.reload();
    });
  }, [initialUser]);

  return initialUser;
}

Đọ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 rồi thay thế hàm getAuthenticatedAppForUser:

export async function getAuthenticatedAppForUser() {
  const authIdToken = (await cookies()).get("__session")?.value;

  // Firebase Server App is a new feature in the JS SDK that allows you to
  // instantiate the SDK with credentials retrieved from the client & has
  // other affordances for use in server environments.
  const firebaseServerApp = initializeServerApp(
    // https://github.com/firebase/firebase-js-sdk/issues/8863#issuecomment-2751401913
    initializeApp(),
    {
      authIdToken,
    }
  );

  const auth = getAuth(firebaseServerApp);
  await auth.authStateReady();

  return { firebaseServerApp, currentUser: auth.currentUser };
}

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 người dùng (nếu có) dưới dạng một prop.

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

Điều này có nghĩa là thành phần <Header> sẽ kết xuất 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ề việc 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 cập nhật đó.

Giờ là lúc bạn triển khai một 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 "Add authentication" (Thêm quy trình xác thực) rồi đẩy 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. 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 trong tiêu đề.
    2. Đăng xuất rồi đăng nhập lại. 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ị. Nội dung này xuất hiện trong nguồn HTML thô do 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 cho 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. Đăng nhập vào ứng dụng web nếu bạn chưa đăng nhập. Sau đó, 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 Cơ sở dữ liệu Firestore, hãy chọn nhà hàng. Bạn sẽ thấy các tài liệu cấp cao nhất trong bộ sưu tậ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 vài tài liệu để khám phá các thuộc tính của tài liệu 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 /> và xem xét lệnh gọi đến hàm getRestaurants. Hàm này sẽ truy xuất danh sách nhà hàng tại thời gian chạy máy chủ. Bạn sẽ triển khai hàm getRestaurants trong các bước sau.
  2. Trong tệp src/lib/firebase/firestore.js, hãy thay thế các 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 cam kết có thông báo cam kết "Read the list of restaurants from Firestore" (Đọc danh sách nhà hàng từ Firestore) rồi đẩy cam kết đó lên 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 các ô trên trang.

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

Khi sử dụng khung Next.js, có thể bạn không biết rõ thời điểm dữ liệu được tải trong thời gian chạy máy chủ hoặc 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 vào 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 JavaScript trong Công cụ cho nhà phát triển

  1. Làm mới ứng dụng web. Danh sách nhà hàng vẫn tải. Thông tin về nhà hàng được trả về trong phản hồi của máy chủ. Khi JavaScript được bật, thông tin nhà hàng sẽ được truy xuất 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 của 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 tải bộ nhà hàng ban đầu 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 kết xuất 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 của ứng dụng và có thể được định cấu hình để bổ sung mã đánh dấu do máy chủ kết xuất.

Để định cấu hình tệp src/components/RestaurantListings.jsx nhằm truyền dữ liệu cho mã đánh dấu được kết xuất phía máy chủ, hãy làm theo các bước sau:

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

Đoạn mã này 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 snapshot 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 bộ sưu tập của nhà hàng.

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

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

Những thay đổi được thực hiện thông qua trang Cơ sở dữ liệu Firestore hiện được phản ánh trong ứng dụng web theo thời gian thực.

  1. Tạo một cam kết có thông báo cam kết "Listen for realtime restaurant updates" (Lắng nghe thông tin cập nhật theo thời gian thực của nhà hàng) và đẩy thông báo đó lên 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 chọn 27ca5d1e8ed8adfe.png > Thêm nhà hàng mẫu. Nếu bạn triển khai đúng chức năng chụp nhanh, 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 đoạn 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()),
  });
};

Đoạn mã này chèn một tài liệu Firestore mới đại diện 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 đã tính.

  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 Thao tác máy chủ Next.js

Next.js Server Action 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") để lấy giá trị văn bản từ tải trọng gửi biểu mẫu.

Để sử dụng Thao tác trên máy chủ Next.js để xử lý biểu mẫu gửi bài đánh giá, hãy làm 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 ở bước tiếp theo.

  1. Trong tệp src/app/actions.js, hãy thay thế hàm handleReviewFormSubmission() bằng đoạn 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á cho nhà hàng

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

Để thêm một 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 cam kết có thông báo cam kết "Cho phép người dùng gửi bài đánh giá nhà hàng" và đẩy 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 biểu tượng 3e19beef78bb0d0e.png.
  5. Chọn đ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 kiếm ngăn Thêm tài liệu để tìm tài liệu về nhà hàng mà bạn đã đánh giá rồi chọn tài liệu đó.
  9. Trong ngăn Bắt đầu thu thập, hãy chọn mức phân loại.
  10. Trong ngăn Thêm tài liệu, hãy tìm tài liệu bạn muốn xem xét để xác minh rằng tài liệu đó đã được chèn như mong đợi.

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

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

Trong phần này, bạn sẽ 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 đã đă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 Cloud Firestore đại diện cho nhà hàng.

Để lưu các 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 đoạn mã chạy khi người dùng tải một 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);
  setRestaurantDetails({ ...restaurantDetails, photo: imageURL });
}

Bạn không cần thay đổi hàm này, nhưng hãy triển khai hành vi của hàm updateRestaurantImage() trong 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 đoạn 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 một tài liệu hiện có về nhà hàng trong Cloud Firestore bằng một URL hình ảnh mới.

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 cam kết có thông báo cam kết "Allow users to change each restaurants' photo" (Cho phép người dùng thay đổi ảnh của từng nhà hàng) rồi đẩy cam kết đó lên 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 xác minh rằng bạn đã đăng nhập và chọn một nhà hàng.
  4. Nhấp vào biểu tượng 7067eb41fea41ff0.png rồi tải một hình ảnh lên từ hệ thống tệp. Hình ảnh của bạn sẽ 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 một tính năng tóm tắt bài đánh giá để người dùng có thể nhanh chóng nắm được ý kiến 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ó một khoá API. Truy cập vào Google AI Studio rồi nhấp vào "Tạo khoá API".
  2. Trong ô nhập "Tìm kiếm dự án trên Google Cloud", hãy chọn dự án Firebase của bạn. Mỗi dự án Firebase đều được hỗ trợ bởi một dự án Google Cloud.
  3. Dịch vụ Lưu trữ ứng dụng tích hợp với Cloud Secret Manager để cho phép bạn lưu trữ an toàn các giá trị nhạy cảm như khoá API:
    1. Trong một thiết bị đầu cuối, 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 liệu khoá bí mật mới có dành cho kiểm thử cục bộ hay phát hành công khai, hãy chọn "Phát hành công khai".
    3. Khi được hỏi liệu bạn có muốn cấp quyền truy cập để tài khoản dịch vụ của phần phụ trợ có thể truy cập vào khoá bí mật hay không, hãy chọn "Có".
    4. 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á Gemini API của bạn hiện được lưu trữ an toàn trong Cloud Secret Manager và có thể truy cập 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 đoạn mã sau:
    export async function GeminiSummary({ restaurantId }) {
      const { firebaseServerApp } = await getAuthenticatedAppForUser();
      const reviews = await getReviewsByRestaurantId(
        getFirestore(firebaseServerApp),
        restaurantId
      );
    
      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 {
        if (!process.env.GEMINI_API_KEY) {
          // Make sure GEMINI_API_KEY environment variable is set:
          // https://firebase.google.com/docs/genkit/get-started
          throw new Error(
            'GEMINI_API_KEY not set. Set it with "firebase apphosting:secrets:set GEMINI_API_KEY"'
          );
        }
    
        // Configure a Genkit instance.
        const ai = genkit({
          plugins: [googleAI()],
          model: gemini20Flash, // set default model
        });
        const { text } = await ai.generate(prompt);
    
        return (
          <div className="restaurant__review_summary">
            <p>{text}</p>
            <p> Summarized with Gemini</p>
          </div>
        );
      } catch (e) {
        console.error(e);
        return <p>Error summarizing reviews.</p>;
      }
    }
    
  2. Tạo một cam kết có thông báo cam kết "Sử dụng AI để tóm tắt bài đánh giá" rồi đẩy cam kết đó 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 của 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 một câu tóm tắt 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 thay đổi trong phần tóm tắt.

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 một ứng dụng Next.js. Cụ thể, bạn đã sử dụng những tính năng sau:

Tìm hiểu thêm