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 ứng dụng web Next.js có tên là friendly Eats, một trang web về các bài đánh giá nhà hàng.

Ứng dụng web Thân thiện

Ứng dụng web hoàn thiện 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 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 đế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. Việc đăng nhập và lưu trữ cố định 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 thiện cho phép người dùng đã đăng nhập tải hình ảnh nhà hàng lên. Nội dung 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 các hình ảnh đã tải lên. Sau đó, URL công khai này được lưu trữ trong tài liệu của nhà hàng có liên quan trên 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ề nhà hàng, bao gồm xếp hạng theo sao và tin nhắn dưới dạ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 qua Cloud Firestore và các truy vấn trên Firestore được áp dụng dựa trên các bộ lọc bạn sử dụng.

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

  • Mộ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 thông qua 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à thiết bị đầu cuối
  • 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ể xem 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ữ 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ữ riêng:

  1. Sử dụng dòng lệnh, tạo một thư mục mới trên máy tính và đổ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 một kho lưu trữ mới trên GitHub: https://github.com/new. Đặt tên bất kỳ mà bạn muốn.
    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 bằng 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 đầ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 Giao diện dòng lệnh (CLI) của Firebase và phiên bản 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 Giao diện dòng lệnh (CLI) của Firebase, 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 npm hoặc sử dụng một chế độ cài đặt khác.

Đăng nhập vào Firebase

  1. Chạy lệnh sau để đăng nhập vào Giao diện dòng lệnh (CLI) của Firebase:
    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 hay 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 Allow (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 Nhập tên dự án của bạn, hãy nhập FriendlyEats Codelab (hoặc tên dự án mà bạn chọn), sau đó nhấp vào Tiếp tục.
  3. Trong cửa sổ phụ Xác nhận gói thanh toán trong Firebase, hãy xác nhận kế hoạch là Làm mờ, rồi nhấp vào Xác nhận kế hoạch
  4. Đối với lớp học lập trình này, bạn không cần sử dụng Google Analytics, nên hãy tắt tuỳ chọn Bật Google Analytics cho dự án này.
  5. Nhấp vào Create project (Tạo dự án).
  6. Chờ dự án của bạn cung cấp, sau đó 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ì sau này bạn sẽ cần đến. Giá trị nhận dạng riêng biệ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 của bạn

Để sử dụng tính năng Lưu trữ ứng dụng, dự án Firebase của bạn phải sử dụng Gói giá linh hoạt, có nghĩa là dự án đó được liên kết với một tài khoản thanh toán Cloud.

  • Tài khoản thanh toán Cloud yêu cầu 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 một khoản tín dụng 300 USD và tài khoản thanh toán dùng thử miễn phí qua đám mây hay không.

Để nâng cấp dự á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 kế hoạch của bạn.
  2. Trong hộp thoại này, hãy chọn Gói linh hoạt, sau đó làm theo hướng dẫn trên màn hình để liên kết dự án của bạn với tài khoản thanh toán Cloud.
    Nếu cần tạo tài khoản thanh toán Cloud, thì 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 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, 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 của ứng dụng, nhập một biệt hiệu dễ nhớ của ứng dụng, chẳng hạn như My Next.js app.
  3. Bỏ chọn hộp kiểm 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 tới bảng điều khiển.

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

Thiết lập phương thức 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 khác, hãy nhấp vào Google > Bật.
  4. Trong hộp văn bản Tên 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. Từ 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 của Firebase, hãy chuyển đến Firestore.
  2. Nhấp vào Tạo cơ sở dữ liệu > Tiếp theo > Bắt đầu ở chế độ thử nghiệm > Tiếp theo.
    Ở 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 không phân phối hoặc hiển thị công khai ứng dụng khi chưa thêm Quy tắc bảo mật cho cơ sở dữ liệu.
  3. Hãy sử dụng vị trí mặc định hoặc chọn vị trí bạn muốn.
    Đối với ứng dụng thực tế, bạn nên chọn vị trí gần người dùng. Xin lưu ý rằng vị trí này không thể thay đổi sau đó và vị trí này cũng sẽ tự động là vị trí của bộ chứa Cloud Storage mặc định của bạn (bước tiếp theo).
  4. Nhấp vào Xong.

Thiết lập Cloud Storage cho Firebase

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến phần Bộ nhớ.
  2. Nhấp vào Bắt đầu > Bắt đầu ở chế độ thử nghiệm > Tiếp theo.
    Ở 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 hiển thị công khai ứng dụng khi chưa thêm Quy tắc bảo mật cho Bộ chứa bộ nhớ.
  3. Bạn phải chọn vị trí cho bộ chứa của mình (do đã thiết lập Firestore ở bước trước).
  4. Nhấp vào Xong.

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

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

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

Bảng sau đây cung cấp 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

Phản ứng của các thành phần đối với 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 Bộ định tuyến ứng dụng Next.js

src/app/restaurant

Trình xử lý tuyến API

package.jsonpackage-lock.json

Các phần phụ thuộc của dự án có npm

next.config.js

Cấu hình dành riêng cho Next.js (các hành động trên 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à ứng dụng

Ứng dụng này là một ứng dụng web Next.js sử dụng App Router (Bộ định tuyến ứng dụng). Tính năng kết xuất 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à 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ể nhận 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ườm rà và có thể thực hiện được nhờ email đại diện đường dẫn.

API dành riêng cho Firebase

Tất cả mã API Firebase đều được bao bọc 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 bài đánh giá và nhà hàng mô phỏng có trong tệp src/lib/randomData.js. Dữ liệu của tệp đó sẽ được tập hợp trong mã trong tệp src/lib/fakeRestaurants.js.

5. Tạo phần phụ trợ 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ợ Lưu trữ ứng dụng kết nối với kho lưu trữ của bạn trên 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 đến 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à 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 lạm dụng.

  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 cung cấp một 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 của bạn:
    firebase deploy --only firestore:rules,storage
    
  3. Nếu bạn được yêu cầu: "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 của bạn

  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 SDK Setup and configuration (Thiết lập và cấu hình SDK), hãy nhấp vào "Add app" (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. Khi kết thúc 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ã của bạn rồi điền các giá trị biến môi trường bằng các giá trị cấu hình trên bảng điều khiển của Firebase.
  5. Trong tệp này, hãy thay thế các cơ sở lưu trú hiện có bằng các cơ sở lưu trú 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:

Bảng điều khiển Lưu trữ ứng dụng ở trạng thái 0, 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. 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. Đặt 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 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 đó từ 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 giây lát, bạn sẽ được đưa đến một trang mới để xem trạng thái của phần phụ trợ Lưu trữ ứng dụng mới!
  7. Sau khi phát hành xong, hãy nhấp vào miền miễn phí trong phần "miền". Quá trình này có thể mất vài phút để bắt đầu hoạt động do lan truyền DNS.

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à quá trình phát hành mới bắt đầu 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 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:

API Firebase

Nội dung mô tả

GoogleAuthProvider

Tạo một phiên bản trì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ã đã gọi các hàm signInWithGooglesignOut.

  1. Tạo một cam kết có thông báo cam kết "Thêm phương thức xác thực của Google" rồi đẩy 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à chờ quá trình phát hành mới của bạn 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 đăng nhập 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 đến máy chủ, chúng ta sẽ dùng service worker. Thay thế các hàm fetchWithFirebaseHeadersgetAuthIdToken bằng đoạn 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 rồi 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ý các thay đổi về phương thức xác thực

Để đăng ký thay đổi về cách 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 đoạn 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 hook trạng thái của 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 nội dung thay đổi

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

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

Điều này có nghĩa là thành phần <Header> kết xuất dữ liệu người dùng (nếu có) trong thời gian chạy của máy chủ. Nếu có bất kỳ bản cập nhật xác thực nào trong vòng đời của trang sau lượt tải trang đầu tiên, thì trình xử lý onAuthStateChanged sẽ xử lý các lượt cập nhật đó.

Giờ đã đến lúc ra mắt một công trình mới và xác minh những gì bạn đã xây dựng.

  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) và đẩy 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à chờ quá trình phát hành mới của bạn hoàn tất.
  3. Xác minh hoạt động 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 xuất hiện trong tiêu đề.
    2. Hãy đă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ị. Thẻ này xuất hiện trong nguồn HTML thô được máy chủ trả về.

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

Ứng dụng web bao gồm 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. Trong ứng dụng web, hãy chọn 2cf67d488d8e6332.pngs > 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 bộ sưu tập nhà hàng, mỗi tài liệu đại diện cho một nhà hàng.
  3. Hãy 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 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 nhà hàng trong 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 cam kết có thông báo cam kết "Đọc danh sách các nhà hàng từ Firestore" và đẩy danh sách này 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à chờ quá trình phát hành mới của bạn 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 của nhà hàng sẽ tải vào thời gian chạy của máy chủ

Khi sử dụng khung Next.js, bạn có thể không thấy rõ thời điểm dữ liệu được tải vào thời gian chạy phía 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 đều tải tại thời điểm 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 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. Các danh sách nhà hàng vẫn tải. Thông tin về nhà hàng sẽ được trả về trong phản hồi của máy chủ. Khi JavaScript được bật, thông tin nhà hàng được cung cấp 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 của Cloud Firestore

Trong phần trước, bạn đã thấy cách tập hợp các 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 để hydrat hoá 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 hydrat hoá mã đánh dấu do máy chủ kết xuất, hãy làm theo các bước sau:

  1. Trong tệp src/components/RestaurantListings.jsx, hãy quan sát mã đã được viết sẵn cho bạn sau đây:
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 tổng quan nhanh này cung cấp cơ chế gọi lại để hệ thống thực hiện lệnh gọi lạ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);

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

Giờ đây, những thay đổi được thực hiện qua trang Firestore Database (Cơ sở dữ liệu khôi phục lại) sẽ 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 nhà hàng" (Nghe thông tin cập nhật về nhà hàng theo thời gian thực) và đẩ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 của Firebase và chờ quá trình phát hành mới của bạn 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 chức năng tổng quan nhanh đúng cách, 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 các 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()),
	});
};

Mã này sẽ chèn một tài liệu mới trên Firestore đại diện cho bài đánh giá mới. Mã này cũng cập nhật tài liệu hiện có trên Firestore đại diện cho nhà hàng bằng các số liệu mới cập nhật cho số điểm 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 đoạn 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 của máy chủ Next.js

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

Để sử dụng Hành động dành cho máy chủ Next.js nhằm xử lý việc gửi biểu mẫu xem xét, 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 đ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á về một 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 bạn được chèn đúng cách vào Cloud Firestore.

Để 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 cam kết có thông điệp cam kết "Cho phép người dùng gửi bài đánh giá về nhà hàng" và đẩy 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à chờ quá trình phát hành mới của bạn 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 xuất hiện ở đầu danh sách bài đánh giá.
  8. Trong Cloud Firestore, hãy tìm tài liệu của nhà hàng mà bạn đã xem xét trong ngăn Thêm tài liệu và chọn tài liệu đó.
  9. Trong ngăn Bắt đầu thu thập, hãy chọn đánh giá.
  10. Trong ngăn Thêm tài liệu, hãy tìm tài liệu cần xem lại để xác minh 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 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 thêm chức năng để có thể thay thế hình ảnh được liên kết với nhà hàng khi bạn đăng nhập. Bạn tải hình ảnh lên Firebase Storage rồi 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 bạn sẽ 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 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 URL hình ảnh mới cho một tài liệu về nhà hàng hiện có trong Cloud Firestore.

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 điệp cam kết "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 của Firebase và chờ quá trình phát hành mới của bạn 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 của bạn. 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 mà bạn tải lên tồn tại trong thư mục.

6cf3f9e2303c931c.png.

10. Tóm tắt các bài đánh giá về 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 nhận xét 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á API Gemini trong Cloud Secret Manager

  1. Để sử dụng API Gemini, bạn cần có một khoá API. Tạo khoá trong Google AI Studio.
  2. Tính năng Lưu trữ ứng dụng tích hợp với Cloud Secret Manager để 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 dòng lệnh, hãy chạy lệnh để tạo 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á API Gemini của bạn trong Google AI Studio.
    2. Khi được hỏi liệu có cầ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ý Cloud Secret. Bạn cũng có thể truy cập vào khoá này trong phần phụ trợ 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 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 cam kết có thông báo cam kết "Use AI to tóm tắt bài đánh giá" và đẩy nội dung đó vào kho lưu trữ GitHub.
  3. Mở trang Lưu trữ ứng dụng trong bảng điều khiển của Firebase và chờ quá trình phát hành mới của bạn hoàn tất.
  4. Mở một trang của một nhà hàng. Ở trên cùng, bạn sẽ thấy một bản tóm tắt dài một câu về tất cả cá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 thay đổi 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 ứng dụng Next.js. Cụ thể, bạn đã sử dụng những nội dung sau:

Tìm hiểu thêm