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 tạo 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 vào 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ì 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. SDK JavaScript của Firebase cung cấp một URL công khai cho hình ảnh đã tải lên. Sau đó, URL công khai này đượ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 số sao và thông báo dạ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 sử 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 bộ lọc đượ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 mới đâ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 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 động của ứng dụng và dựa vào Firebase CLI.

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 một thiết bị đầu cuối, hãy tạo một thư mục mới trên máy tính 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 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.
  5. Sao chép URL mới mà GitHub tạo cho bạn. Trạng thái này sẽ có dạng như một trong những trạng thái 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ế URL kho lưu trữ thực tế của bạn cho phần giữ chỗ <REPOSITORY_URL>.
    git remote add origin <REPOSITORY_URL>
    
    git push -u origin main
    
  7. 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 một 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 có 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 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ổ bật lên Xác nhận gói thanh toán Firebase, hãy xác nhận gói 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, hãy chuyển đến phần Cài đặt dự án. Ghi lại mã dự án vì bạn sẽ cần mã này sau. 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ữ trên đám mây và Lưu trữ ứng dụng Firebase, dự án Firebase của bạn cần phải sử dụng gói thanh toán 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.

  • Tài khoản Cloud Billing cần 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 trị giá 300 USD và tài khoản Cloud Billing 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ó khoản tín dụng Google Cloud nào không.

Để 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 tài khoản Cloud Billing với dự án của bạn.
    Nếu cần tạo tài khoản Cloud Billing 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 ứ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, hãy nhập một biệt hiệu ứng dụng dễ nhớ, chẳng hạn như My Next.js app.
  3. Đừng đánh dấu vào hộp Cũng 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 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 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 công khai cho 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 Support email for project (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 Firebase, hãy mở rộng mục Build (Tạo) rồi chọn Firestore database (Cơ sở dữ liệu Firestore).
  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 một vị trí cho cơ sở dữ liệu, rồi nhấp vào Tiếp theo.
    Đối với ứng dụng thực tế, bạn nên chọn một vị trí gần với người dùng.
  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 các Quy tắc bảo mật để bảo mật dữ liệu của mình. Đừ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 cơ sở dữ liệu.
  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 Firebase, hãy mở rộng mục Bản dựng rồi chọn 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. Đừng phân phối hoặc hiển thị công khai ứng dụng mà không thêm Quy tắc bảo mật cho bộ nhớ.
  5. Nhấp vào Tạo.

4. Xem lại cơ sở mã khởi động

Trong phần này, bạn sẽ xem lại một số phần của cơ sở mã khởi động của ứng dụng mà bạn sẽ thêm chức năng 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ề thư mục và cấu trúc 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

Trình xử lý 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 (các hành động của máy chủ được 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 Trình định tuyến ứng dụng. Tính năng hiển thị phía 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 cồng kềnh và có thể thực hiện được nhờ bí danh đường dẫn.

API dành riêng cho Firebase

Tất cả mã API Firebase đề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ợ của dịch vụ Lưu trữ ứng dụng để theo dõi một nhánh trên kho lưu trữ git.

Khi kết thúc phần này, bạn sẽ có một phần phụ trợ 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 phiên bản mới của ứng dụng mỗi khi bạn đẩy một thay đổi mới vào nhánh main.

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

Mã này đã có các bộ 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à 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 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, hãy chạy lệnh sau trong dòng lệnh:
    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. Di chuyển xuống phần Ứng dụng của bạn, rồi chọn Ứng dụng web Firebase mà bạn đã tạo trước đó trong lớp học lập trình này.
  3. Sao chép biến firebaseConfig, sao chép các thuộc tính và 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 tính năng tự động triển khai
  4. Đặt tên cho phần phụ trợ là friendlyeats-codelab.
  5. Trong phần "Tạo hoặc liên kết ứng dụng web Firebase", hãy chọn ứng dụng web mà bạn đã định cấu hình trước đó trong 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 khi đẩy một thay đổi 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à quá trình triển khai bắt đầu trong bảng điều khiển Firebase. Trang web của bạn sẽ tự động cập nhật sau khi quá trình triển khai hoàn tất.

6. Thêm tính năng 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 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, 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:

Firebase API

Nội dung mô tả

GoogleAuthProvider

Tạo một thực thể nhà cung cấp 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ã đã 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 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 bạn đã đăng nhập thành công hay chưa.

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 một trình chạy dịch vụ. 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 thông báo về các thay đổi đối với quy trình 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 hiển thị tiêu đề và truyền vào người dùng (nếu có) dưới dạng một thuộc tính.

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

Điều này có nghĩa 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 thay đổi có thông báo thay đổi "Hiện trạng thái đăng nhập" 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. 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. Trang 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 nhiều người dùng.
    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ị. Mã này xuất hiện trong nguồn HTML thô được trả về từ máy chủ.

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 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 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 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 lại 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 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ế các hàm applyQueryFiltersgetRestaurants bằng 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 "Đọc danh sách nhà hàng từ Firestore" 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 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 thẻ thông tin 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ở DevTools và 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 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 về nhà hàng sẽ được cấp dữ liệu 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.

Theo dõi 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 để làm mới mã đánh dấu do máy chủ hiển thị.

Để đị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 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;
}

Các thay đổi được thực hiện thông qua trang Cơ sở dữ liệu Firestore hiện 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 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 chọn 27ca5d1e8ed8adfe.png > Thêm nhà hàng mẫu. Nếu bạn triển khai đúng chức năng ảnh chụp nhanh, thì 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 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 đượ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 máy chủ Next.js cung cấp một API thuận tiện để truy cập 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 máy chủ Next.js nhằm xử lý việc gửi biểu mẫu đá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 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á. 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 thay đổi có thông báo thay đổi "Cho phép người dùng gửi bài đánh giá nhà hàng" 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. 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 đ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 tài liệu về nhà hàng mà bạn đã xem xét trong ngăn Thêm tài liệu rồi 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 Thêm tài liệu, hãy tìm tài liệu mà 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 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 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 thay đổi có thông báo thay đổi "Cho phép người dùng thay đổi ảnh của từng nhà hàng" 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 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.png rồi tải 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 tính năng tóm tắt bài đánh giá để người dùng có thể nhanh chóng hiểu được mọi người nghĩ gì 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 Trình quản lý bí mật trên đám mây

  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 Trình quản lý bí mật trên đám mây để cho phé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 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 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. Ở đầu trang, bạn sẽ thấy một câu tóm tắt tất cả bài đánh giá trên trang.
  5. Thêm 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