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 Firebase client SDK khác, 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 phiên bản ứ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). SDK 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 kết xuất phía máy khách (CSR) / phân chia kết xuất phía máy chủ. Những 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 ngữ cảnh người dùng, khác với SDK của Firebase dành cho quản trị viên có toàn 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 một 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 thời gian chạy không phải trình duyệt khác (chẳng hạn như trình chạy đám mây) tối ưu hoá thời gian khởi tạo 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 những 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 mức hao tổn khi khởi động và mức phân bổ bộ nhớ không cần thiết. Khi deleteApp được gọi trên một thực thể FirebaseServerApp, nó sẽ giảm số lượt tham chiếu và thực thể sẽ được giải phóng sau khi số lượt tham chiếu đạt đến 0.

Dọn dẹp các phiên bản FirebaseServerApp

Bạn có thể gặp khó khăn khi biết thời điểm gọi deleteApp trên một thực thể FirebaseServerApp, đặc biệt 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 thông tin 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ượng thông tin tham chiếu khi khung hình thu hồi đối tượng tiêu đề. Thao tác này sẽ tự động dọn dẹp phiên bản FirebaseServerApp của bạn.

Sau đâ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 phiên bản của FirebaseServerApp được khởi chạy bằng mã thông báo Auth ID, phiên bản 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 phiên bản của SDK Xác thực Firebase đượ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 mã nhận dạng Auth cho phép các ứng dụng sử dụng mọi phương thức đăng nhập của Auth trên ứng dụng, đảm bảo rằng phiên hoạt động 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, tính năng 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. Điều này sẽ 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 tính năng Kiểm tra ứng dụng dựa trên một phiên bản Kiểm tra ứng dụng SDK mà Firebase SDK sử dụng để gọi getToken nội bộ. Sau đó, mã thông báo nhận được sẽ được đưa vào các yêu cầu gửi đến tất cả dịch vụ Firebase, cho phép giao diện người dùng xác thực ứng dụng.

Tuy nhiên, vì SDK Kiểm tra ứng dụng cần một trình duyệt để truy cập vào các phương pháp phỏng đoán cụ thể cho quy trình 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 lựa chọn 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 đó sẽ được các SDK sản phẩm của Firebase sử dụng khi gọi các dịch vụ của Firebase, giúp bạn không cần đến 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 của ứng dụng đến giai đoạn hiển thị phía máy chủ

Để truyền mã thông báo Auth ID (và mã thông báo Kiểm tra ứng dụng) đã xác thực từ ứng dụng đến giai đoạn kết xuất phía máy chủ (SSR), hãy sử dụng một 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à nối các mã thông báo vào tiêu đề của yêu cầu.

Hãy tham khảo Quản lý phiên bằng các trình chạy dịch vụ để biết thông tin triển khai tham chiếu về một trình chạy dịch vụ Xác thực Firebase. Bạn cũng có thể xem 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 tạo 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à máy khách để tối ưu hoá hiệu suất và trải nghiệm người dùng. Firestore SDK cung cấp các công cụ chuyển đổi tuần tự cho phép bạn chụp nhanh và chụp 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. Quy trình này loại bỏ các lần tìm nạp dư thừa bằng cách cho phép ứng dụng khôi phục 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 vẫ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 kiểu 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ể chuyển đổi tuần tự. Các đối tượng 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ủ đến máy khách thông qua các cơ chế khung tiêu chuẩn hoặc dưới dạng các tham số cho các thành phần trải rộng. 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 kiểu 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 chuyển đổi tuần tự thành loại dữ liệu Firestore có thể hoạt động.

Ví dụ: thao tác sau đây sẽ chuyển đổi một 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á các Snapshot Firestore

Tương tự như các loại dữ liệu Firestore, bạn có thể chuyển đổi các thực thể của DocumentSnapshotQuerySnapshot thành chuỗi bằng cách sử dụng toJSON. Tuy nhiên, để giải tuần tự hoá các đối tượng 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ụ: bạn có thể chuyển đổi kết quả querySnapshot của một thao tác query thành chuỗi 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 chuyển đổi tuần tự ngược:

import {
  querySnapshotFromJSON
} from 'firebase/firestore';

// deserializedSnapshot is an object of type QuerySnapshot:

const deserializedSnapshot =
  querySnapshotFromJSON(firestore, querySnapshotJson);

Người nghe có ảnh chụp nhanh theo chuỗi

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 thông tin cập nhật theo thời gian thực về thông tin đó.

Nếu ứng dụng của bạn yêu cầu những nội dung cập nhật theo thời gian thực này, bạn có thể dùng hàm onSnapshotResume để khởi chạy SnapshotListener Firestore bằng dữ liệu Snapshot được chuyển đổi tuần tự. 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);