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 khác của Firebase dành cho ứng dụng, thì có thể bạn đã quen 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. Để hỗ trợ 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 quá trình phân chia giữa kết xuất phía máy khách (CSR) và 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 cải thiện 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 Firebase Admin SDK có đầy đủ quyền quản trị.
  • Cho phép sử dụng tính năng Kiểm tra ứng dụng trong môi trường SSR.
  • Tiếp tục phiên xác thực Firebase đã đượ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 môi trường thời gian chạy không phải trình duyệt khác (chẳng hạn như trình chạy 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 số lượt tham chiếu. Nếu một ứng dụng gọi initializeServerApp bằng 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ượt tham chiếu và được giải phóng sau khi số lượt 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 xác định 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 tham chiếu đến một đối tượng có thời gian tồn tại trong 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ượt 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 của bạn.

Sau đây là 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 chạy 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 Firebase Auth SDK được khởi chạy bằng đố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 Auth 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 người dùng tương tác. Ngoài ra, việc này cho phép chuyển 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 tính năng Kiểm tra ứng dụng trong môi trường SSR

Việc thực thi tính năng Kiểm tra ứng dụng dựa vào một thực thể SDK Kiểm tra ứng dụng mà các SDK Firebase 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 đối với 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 SDK này không thể được khởi chạ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 chạy 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, giúp loại bỏ nhu cầu về 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 sang 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 sang 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 đề của yêu cầu.

Hãy tham khảo bài viết Quản lý phiên bằng trình chạy dịch vụ để biết cách triển khai tham chiếu của trình chạy dịch vụ Firebase Auth. Ngoài ra, hãy xem bài viết Các 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 quá trình khởi chạy FirebaseServerApp.

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ụ tuần tự hoá cho phép bạn chụp ảnh 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 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 tuần tự hoá 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 sang đị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 đố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ủ sang ứ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 quá trình phân chia. 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á sang loại dữ liệu Firestore có thể hoạt động.

Ví dụ: mã sau đây sẽ 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 cách sử dụ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 rất 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 SnapshotListener Firestore 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);