Sử dụng Firebase trong các ứng dụng web động có SSR (Hiển thị phía máy chủ)

Nếu đã từng làm việc với Firebase JS SDK hoặc các SDK ứng dụng Firebase khác, thì có lẽ bạn đã quen thuộc với giao diện FirebaseApp và cách sử dụng giao diện này để định cấu hình các thực thể ứng dụng. Để tạo điều kiện thuận lợi cho các thao tác tương tự ở phía máy chủ, Firebase cung cấp FirebaseServerApp.

FirebaseServerApp là một biến thể của FirebaseApp để sử dụng trong môi trường kết xuất phía máy chủ (SSR). Biến thể này bao gồm các công cụ để tiếp tục các phiên Firebase trải dài trên ranh giới kết xuất phía máy khách (CSR) / kết xuất phía máy chủ. Các công cụ và chiến lược này có thể giúp nâng cao các ứng dụng web động được xây dựng bằng Firebase và triển khai trong các môi trường của Google như Firebase App Hosting.

Sử dụng FirebaseServerApp để:

  • Thực thi mã phía máy chủ trong bối cảnh người dùng, trái ngược với SDK của Firebase dành cho quản trị viên có đầy đủ quyền quản trị.
  • Cho phép sử dụng Kiểm tra ứng dụng trong môi trường SSR.
  • Tiếp tục phiên Firebase Auth được tạo trong ứng dụng.

Vòng đời FirebaseServerApp

Các khung kết xuất phía máy chủ (SSR) và các thời gian chạy không phải trình duyệt khác như trình thực thi trên đám mây tối ưu hoá thời gian khởi chạy bằng cách sử dụng lại các tài nguyên trên nhiều lần thực thi. FirebaseServerApp được thiết kế để phù hợp với các môi trường này bằng cách sử dụng cơ chế đếm tham chiếu. Nếu một ứng dụng gọi initializeServerApp với các tham số giống như initializeServerApp trước đó, thì ứng dụng đó sẽ nhận được cùng một thực thể FirebaseServerApp đã được khởi chạy. Điều này giúp giảm bớt chi phí khởi chạy và phân bổ bộ nhớ không cần thiết. Khi deleteApp được gọi trên một thực thể FirebaseServerApp, thực thể này sẽ giảm số lượng tham chiếu và thực thể sẽ được giải phóng sau khi số lượng tham chiếu đạt đến 0.

Dọn dẹp các thực thể FirebaseServerApp

Bạn có thể gặp khó khăn trong việc biết thời điểm gọi deleteApp trên một thực thể FirebaseServerApp, đặc biệt là nếu bạn đang chạy nhiều thao tác không đồng bộ song song. Trường releaseOnDeref của FirebaseServerAppSettings giúp đơn giản hoá việc này. Nếu bạn chỉ định releaseOnDeref một tham chiếu đến một đối tượng có thời gian tồn tại của phạm vi yêu cầu (ví dụ: đối tượng tiêu đề của yêu cầu SSR), thì FirebaseServerApp sẽ giảm số lượng tham chiếu khi khung thu hồi đối tượng tiêu đề. Điều này sẽ tự động dọn dẹp thực thể FirebaseServerApp.

Dưới đây là một ví dụ về cách sử dụng releaseOnDeref:

/// Next.js
import { headers } from 'next/headers'
import { FirebaseServerAppSettings, initializeServerApp} from "firebase/app";

export default async function Page() {
  const headersObj = await headers();
  let appSettings: FirebaseServerAppSettings = {};
  appSettings.releaseOnDeref = headersObj;
  const serverApp = initializeServerApp(firebaseConfig, appSettings);
  ...
}

Tiếp tục các phiên đã xác thực được tạo trên ứng dụng

Khi một thực thể của FirebaseServerApp được khởi động bằng mã thông báo ID Xác thực, thực thể này sẽ cho phép kết nối các phiên người dùng đã xác thực giữa môi trường kết xuất phía máy khách (CSR) và môi trường kết xuất phía máy chủ (SSR). Các thực thể của SDK Firebase Auth được khởi chạy bằng một đối tượng FirebaseServerApp chứa mã thông báo ID xác thực sẽ cố gắng đăng nhập người dùng khi khởi chạy mà không cần ứng dụng gọi bất kỳ phương thức đăng nhập nào.

Việc cung cấp mã thông báo ID xác thực cho phép các ứng dụng sử dụng bất kỳ phương thức đăng nhập nào của Xác thực trên ứng dụng, đảm bảo rằng phiên tiếp tục ở phía máy chủ, ngay cả đối với những phương thức đăng nhập yêu cầu lượt tương tác của người dùng. Ngoài ra, phương thức này cho phép giảm tải các thao tác chuyên sâu sang máy chủ, chẳng hạn như các truy vấn Firestore đã xác thực, giúp cải thiện hiệu suất kết xuất của ứng dụng.

/// Next.js
import { initializeServerApp } from "firebase/app";
import { getAuth } from "firebase/auth";

// Replace the following with your app's
// Firebase project configuration
const firebaseConfig = {
  // ...
};

const firebaseServerAppSettings = {
  authIdToken: token  // See "Pass client tokens to the server side
                      // rendering phase" for an example on how transmit
                      // the token from the client and the server.
}

const serverApp =
  initializeServerApp(firebaseConfig,
                      firebaseServerAppSettings);
const serverAuth = getAuth(serverApp);

// FirebaseServerApp and Auth will now attempt
// to sign in the current user based on provided
// authIdToken.

Sử dụng Kiểm tra ứng dụng trong môi trường SSR

Việc thực thi Kiểm tra ứng dụng dựa vào một thực thể SDK Kiểm tra ứng dụng mà Firebase SDK sử dụng để gọi getToken nội bộ. Sau đó, mã thông báo kết quả sẽ được đưa vào các yêu cầu gửi đến tất cả các dịch vụ Firebase, cho phép phần phụ trợ xác thực ứng dụng.

Tuy nhiên, vì SDK Kiểm tra ứng dụng cần có trình duyệt để truy cập vào các phương pháp phỏng đoán cụ thể nhằm xác thực ứng dụng, nên không thể khởi chạy SDK này trong môi trường máy chủ.

FirebaseServerApp cung cấp một giải pháp thay thế. Nếu mã thông báo Kiểm tra ứng dụng do ứng dụng tạo được cung cấp trong quá trình khởi động FirebaseServerApp, thì mã thông báo này sẽ được các SDK sản phẩm Firebase sử dụng khi gọi các dịch vụ Firebase, loại bỏ nhu cầu về một thực thể SDK Kiểm tra ứng dụng.

/// Next.js
import { initializeServerApp } from "firebase/app";

// Replace the following with your app's
// Firebase project configuration
const firebaseConfig = {
  // ...
};

const firebaseServerAppSettings = {
  appCheckToken: token // See "Pass client tokens to the server side
                       // rendering phase" for an example on how transmit
                       // the token from the client and the server.
}

const serverApp =
  initializeServerApp(firebaseConfig,
                      firebaseServerAppSettings);

// The App Check token will now be appended to all Firebase service requests.

Truyền mã thông báo ứng dụng đến giai đoạn kết xuất phía máy chủ

Để truyền mã thông báo ID Xác thực đã xác thực (và mã thông báo Kiểm tra ứng dụng) từ ứng dụng đến giai đoạn kết xuất phía máy chủ (SSR), hãy sử dụng trình chạy dịch vụ. Phương pháp này liên quan đến việc chặn các yêu cầu tìm nạp kích hoạt SSR và thêm mã thông báo vào tiêu đề yêu cầu.

Hãy tham khảo phần Quản lý phiên bằng trình chạy dịch vụ workers để biết cách triển khai tham chiếu trình chạy dịch vụ Firebase Auth. Bạn cũng có thể xem phần Thay đổi phía máy chủ để biết mã minh hoạ cách phân tích cú pháp các mã thông báo này từ tiêu đề để sử dụng trong FirebaseServerApp khởi chạy.

Sử dụng Firestore trong môi trường SSR

Khi xây dựng các ứng dụng web bằng tính năng Kết xuất phía máy chủ (SSR), bạn thường cần chia sẻ dữ liệu giữa máy chủ và ứng dụng để tối ưu hoá hiệu suất và trải nghiệm người dùng. SDK Firestore cung cấp các công cụ chuyển đổi tuần tự cho phép bạn chụp ảnh chụp nhanh và các loại dữ liệu cụ thể trên máy chủ, đồng thời truyền trực tiếp các loại dữ liệu này đến các thành phần phía máy khách. Quá trình này giúp loại bỏ các lần tìm nạp dư thừa bằng cách cho phép ứng dụng bổ sung trạng thái bằng dữ liệu được tìm nạp trước trong giai đoạn SSR. Ngoài ra, bạn có thể chuyển đổi từ các trạng thái được chuyển đổi tuần tự này sang trình nghe theo thời gian thực, đảm bảo ứng dụng của bạn luôn được đồng bộ hoá với cơ sở dữ liệu.

Phần này mô tả cách sử dụng lại dữ liệu được truy xuất trong giai đoạn kết xuất phía máy chủ (SSR) trong các thành phần phía máy khách.

Tuần tự hoá các loại dữ liệu

Một số loại dữ liệu Firestore cung cấp phương thức toJSON để chuyển đổi dữ liệu của chúng thành định dạng có thể tuần tự hoá. Các loại dữ liệu này bao gồm các thực thể của các đối tượng như Bytes, GeoPoint, TimestampVectorValue.

Sau khi có dữ liệu ở định dạng JSON, bạn có thể truyền dữ liệu đó từ máy chủ đến ứng dụng thông qua các cơ chế khung tiêu chuẩn hoặc dưới dạng tham số cho các thành phần trải dài trên ranh giới. Ví dụ:

import {
  Bytes
} from 'firebase/firestore';

const BYTES_DATA = new Uint8Array([0, 1, 2, 3, 4, 5]);
const bytes = Bytes.fromUint8Array(BYTES_DATA);
const bytesJSON = bytes.toJSON();

Giải tuần tự hoá các loại dữ liệu

Các loại dữ liệu Firestore bao gồm phương thức tĩnh fromJSON để chuyển đổi dữ liệu được tuần tự hoá thành loại dữ liệu Firestore có thể hoạt động.

Ví dụ: mã sau đây giải tuần tự hoá loại dữ liệu Bytes:

import {
  Bytes
} from 'firebase/firestore';

// Assuming the same `bytesJSON` variable from the previous example.
const deserializedBytes = Bytes.fromJSON(bytesJSON);

Tuần tự hoá và giải tuần tự hoá ảnh chụp nhanh Firestore

Tương tự như các loại dữ liệu Firestore, bạn có thể tuần tự hoá các thực thể của DocumentSnapshotQuerySnapshot bằng toJSON. Tuy nhiên, để giải tuần tự hoá các thực thể này, bạn phải sử dụng các hàm độc lập documentSnapshotFromJSONquerySnapshotFromJSON thay vì phương thức fromJSON tĩnh.

Ví dụ: kết quả querySnapshot của thao tác query có thể được tuần tự hoá bằng phương thức toJSON:

import {
  collection,
  getDocs,
  query,
  querySnapshotFromJSON
} from 'firebase/firestore';
// Assuming a configured instance of Firestore in the variable `firestore`.
const queryRef = query(collection(firestore, QUERY_PATH));
const querySnapshot = await getDocs(queryRef);
const querySnapshotJson = querySnapshot.toJSON();

Sau đó, dữ liệu này có thể được giải tuần tự hoá:

import {
  querySnapshotFromJSON
} from 'firebase/firestore';

// deserializedSnapshot is an object of type QuerySnapshot:

const deserializedSnapshot =
  querySnapshotFromJSON(firestore, querySnapshotJson);

Trình nghe có ảnh chụp nhanh được tuần tự hoá

Mặc dù dữ liệu được truy vấn trong giai đoạn SSR có giá trị cho quá trình kết xuất CSR ban đầu, nhưng bạn vẫn có thể cần theo dõi dịch vụ Firestore để biết các bản cập nhật theo thời gian thực cho thông tin đó.

Nếu ứng dụng của bạn yêu cầu các bản cập nhật theo thời gian thực này, bạn có thể sử dụng onSnapshotResume hàm để khởi chạy Firestore SnapshotListener bằng dữ liệu Snapshot được tuần tự hoá. Ví dụ:

const observer = {
  next: (qs) => {
    console.log("onSnapshot invoked: ", qs.data());
  },
  error: (e) => {
    console.log("error callback invoked: ", e.toString());
  }
};
const unsubscribe = onSnapshotResume(firestore, querySnapshotJson, observer);