ใช้ Firebase ในเว็บแอปแบบไดนามิกด้วย SSR (การแสดงผลฝั่งเซิร์ฟเวอร์)

หากเคยใช้ Firebase JS SDK หรือ Firebase Client SDK อื่นๆ คุณอาจคุ้นเคยกับอินเทอร์เฟซ FirebaseApp และวิธีใช้เพื่อกำหนดค่าอินสแตนซ์ของแอป Firebase มี FirebaseServerApp เพื่ออำนวยความสะดวกในการดำเนินการที่คล้ายกันในฝั่งเซิร์ฟเวอร์

FirebaseServerApp เป็นตัวแปรของ FirebaseApp สำหรับใช้ในสภาพแวดล้อมการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) ซึ่งรวมถึงเครื่องมือในการดำเนินการเซสชัน Firebase ต่อไป ที่ครอบคลุมการแสดงผลฝั่งไคลเอ็นต์ (CSR) / การแสดงผลฝั่งเซิร์ฟเวอร์ เครื่องมือและกลยุทธ์เหล่านี้ช่วยปรับปรุงเว็บแอปแบบไดนามิกที่สร้างด้วย Firebase และ ติดตั้งใช้งานในสภาพแวดล้อมของ Google เช่น Firebase App Hosting ได้

ใช้ FirebaseServerApp เพื่อทำสิ่งต่อไปนี้

  • เรียกใช้โค้ดฝั่งเซิร์ฟเวอร์ภายในบริบทของผู้ใช้ ซึ่งแตกต่างจาก Firebase Admin SDK ที่มีสิทธิ์การดูแลระบบเต็มรูปแบบ
  • เปิดใช้ App Check ในสภาพแวดล้อม SSR
  • ดำเนินการต่อในเซสชัน Firebase Auth ที่สร้างในไคลเอ็นต์

วงจรการใช้งาน FirebaseServerApp

เฟรมเวิร์กการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) และรันไทม์อื่นๆ ที่ไม่ใช่เบราว์เซอร์ เช่น Cloud Worker จะเพิ่มประสิทธิภาพเวลาเริ่มต้นโดยการนำทรัพยากรกลับมาใช้ใหม่ในการดำเนินการหลายครั้ง FirebaseServerApp ออกแบบมาเพื่อรองรับสภาพแวดล้อมเหล่านี้ โดยใช้กลไกการนับอ้างอิง หากแอปเรียกใช้ initializeServerApp ด้วยพารามิเตอร์เดียวกันกับ initializeServerApp ก่อนหน้า แอปจะได้รับอินสแตนซ์ FirebaseServerApp เดียวกันกับที่ เริ่มต้นไว้แล้ว ซึ่งจะช่วยลดโอเวอร์เฮดในการเริ่มต้นที่ไม่จำเป็น และการจัดสรรหน่วยความจำ เมื่อเรียกใช้ deleteApp ในอินสแตนซ์ FirebaseServerApp ระบบจะลดจำนวนการอ้างอิง และจะปล่อยอินสแตนซ์หลังจากที่ จำนวนการอ้างอิงเป็น 0

การล้างข้อมูลอินสแตนซ์ FirebaseServerApp

การทราบว่าเมื่อใดควรเรียกใช้ deleteApp ในอินสแตนซ์ FirebaseServerApp อาจเป็นเรื่องยาก โดยเฉพาะอย่างยิ่งหากคุณเรียกใช้การดำเนินการแบบอะซิงโครนัสหลายรายการแบบ ขนานกัน releaseOnDeref ของ FirebaseServerAppSettings ช่วยให้กระบวนการนี้ง่ายขึ้น หากคุณกำหนดreleaseOnDerefการอ้างอิงไปยังออบเจ็กต์ที่มี อายุการใช้งานของขอบเขตคำขอ (เช่น ออบเจ็กต์ส่วนหัวของคำขอ SSR ) FirebaseServerAppจะลดจำนวนการอ้างอิงเมื่อเฟรมเวิร์กเรียกคืนออบเจ็กต์ส่วนหัว ซึ่งจะล้างข้อมูลในอินสแตนซ์ FirebaseServerApp โดยอัตโนมัติ

ตัวอย่างการใช้งาน 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);
  ...
}

กลับมาใช้เซสชันที่ได้รับการตรวจสอบสิทธิ์ซึ่งสร้างขึ้นในไคลเอ็นต์

เมื่อเริ่มต้นอินสแตนซ์ของ FirebaseServerApp ด้วยโทเค็นรหัสการตรวจสอบสิทธิ์ ระบบจะ เปิดใช้การเชื่อมโยงเซสชันของผู้ใช้ที่ได้รับการตรวจสอบสิทธิ์ระหว่างสภาพแวดล้อมการแสดงผลฝั่งไคลเอ็นต์ (CSR) และการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) อินสแตนซ์ของ Firebase Auth SDK ที่เริ่มต้นด้วยออบเจ็กต์ FirebaseServerApp ซึ่งมีโทเค็นรหัส Auth จะพยายามลงชื่อเข้าใช้ผู้ใช้เมื่อเริ่มต้นโดยไม่ต้องให้แอปพลิเคชันเรียกใช้เมธอดการลงชื่อเข้าใช้ใดๆ

การระบุโทเค็นรหัสการให้สิทธิ์จะช่วยให้แอปใช้วิธีการลงชื่อเข้าใช้ของ Auth บนไคลเอ็นต์ได้ ซึ่งจะช่วยให้เซสชันทำงานต่อไปในฝั่งเซิร์ฟเวอร์ได้ แม้แต่วิธีการลงชื่อเข้าใช้ที่ต้องมีการโต้ตอบของผู้ใช้ก็ตาม นอกจากนี้ ยังช่วยให้ การทำงานที่ต้องใช้ทรัพยากรมากจะถูกส่งไปยังเซิร์ฟเวอร์ เช่น คำค้นหา Firestore ที่ผ่านการตรวจสอบสิทธิ์ ซึ่งจะช่วยปรับปรุงประสิทธิภาพการแสดงผลของแอป

/// 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.

ใช้ App Check ในสภาพแวดล้อม SSR

การบังคับใช้ App Check จะขึ้นอยู่กับอินสแตนซ์ App Check SDK ที่ Firebase SDK ใช้ เพื่อเรียก getToken ภายใน จากนั้นระบบจะรวมโทเค็นที่ได้ไว้ในคำขอ ไปยังบริการทั้งหมดของ Firebase เพื่อให้แบ็กเอนด์ตรวจสอบแอปได้

อย่างไรก็ตาม เนื่องจาก App Check SDK ต้องใช้เบราว์เซอร์เพื่อเข้าถึงฮิวริสติกที่เฉพาะเจาะจง สำหรับการตรวจสอบแอป จึงเริ่มต้นในสภาพแวดล้อมของเซิร์ฟเวอร์ไม่ได้

FirebaseServerApp มีทางเลือกให้ หากมีการระบุโทเค็น App Check ที่ไคลเอ็นต์สร้างขึ้นในระหว่างFirebaseServerAppการเริ่มต้น SDK ของผลิตภัณฑ์ Firebase จะใช้โทเค็นดังกล่าวเมื่อเรียกใช้บริการ Firebase ซึ่งจะช่วยลดความจำเป็น ในการใช้อินสแตนซ์ App Check SDK

/// 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.

ส่งโทเค็นไคลเอ็นต์ไปยังระยะการแสดงผลฝั่งเซิร์ฟเวอร์

หากต้องการส่งโทเค็นรหัสการตรวจสอบสิทธิ์ที่ได้รับการตรวจสอบสิทธิ์ (และโทเค็น App Check) จากไคลเอ็นต์ ไปยังเฟสการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) ให้ใช้ Service Worker แนวทางนี้ เกี่ยวข้องกับการสกัดกั้นคำขอ Fetch ที่ทริกเกอร์ SSR และต่อท้ายโทเค็น ในส่วนหัวของคำขอ

ดูการใช้งานอ้างอิงของ Service Worker ของ Firebase Auth ได้ที่การจัดการเซสชันด้วย Service Worker ดูการเปลี่ยนแปลงฝั่งเซิร์ฟเวอร์สำหรับโค้ด ที่แสดงวิธีแยกวิเคราะห์โทเค็นเหล่านี้จากส่วนหัวเพื่อใช้ในการเริ่มต้นFirebaseServerApp

ใช้ Firestore ในสภาพแวดล้อม SSR

เมื่อสร้างเว็บแอปพลิเคชันด้วยการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) คุณมักจะต้อง แชร์ข้อมูลระหว่างเซิร์ฟเวอร์กับไคลเอ็นต์เพื่อเพิ่มประสิทธิภาพและประสบการณ์ของผู้ใช้ Firestore SDK มีเครื่องมือการซีเรียลไลซ์ที่ช่วยให้คุณ บันทึกสแนปชอตและประเภทข้อมูลที่เฉพาะเจาะจงในเซิร์ฟเวอร์ และส่งไปยังคอมโพเนนต์ฝั่งไคลเอ็นต์ได้โดยตรง กระบวนการนี้จะช่วยลดการดึงข้อมูลที่ซ้ำซ้อนโดย อนุญาตให้ไคลเอ็นต์ไฮเดรตสถานะโดยใช้ข้อมูลที่ดึงข้อมูลล่วงหน้าในระหว่างระยะ SSR นอกจากนี้ คุณยังเปลี่ยนจากสถานะที่ทำให้เป็นอนุกรมเหล่านี้ไปเป็น เครื่องมือฟังแบบเรียลไทม์ได้ เพื่อให้มั่นใจว่าแอปพลิเคชันจะยังคงซิงค์กับ ฐานข้อมูล

ส่วนนี้จะอธิบายวิธีนำข้อมูลที่ดึงมาในระหว่างเฟสการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) มาใช้ซ้ำภายในคอมโพเนนต์ฝั่งไคลเอ็นต์

ซีเรียลไลซ์ประเภทข้อมูล

ข้อมูลบางประเภทใน Firestore มีtoJSONเมธอดในการแปลงข้อมูล เป็นรูปแบบที่ทำให้เป็นอนุกรมได้ ซึ่งรวมถึงอินสแตนซ์ของออบเจ็กต์ เช่น Bytes, GeoPoint, Timestamp และ VectorValue

เมื่อมีข้อมูลในรูปแบบ JSON แล้ว คุณจะส่งข้อมูลจากเซิร์ฟเวอร์ไปยังไคลเอ็นต์ผ่านกลไกเฟรมเวิร์กมาตรฐาน หรือเป็นพารามิเตอร์ไปยังคอมโพเนนต์ที่ครอบคลุมส่วนต่างๆ ได้ เช่น

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();

ยกเลิกการซีเรียลไลซ์ประเภทข้อมูล

ประเภทข้อมูล Firestore มีเมธอดแบบคงที่ fromJSON เพื่อแปลงข้อมูลที่ ทำให้เป็นอนุกรมเป็นประเภทข้อมูล Firestore ที่ใช้งานได้

เช่น ตัวอย่างต่อไปนี้จะยกเลิกการซีเรียลไลซ์Bytesประเภทข้อมูล

import {
  Bytes
} from 'firebase/firestore';

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

แปลงอนุกรมและยกเลิกการแปลงอนุกรมของสแนปชอต Firestore

คุณสามารถจัดรูปแบบอินสแตนซ์ของ DocumentSnapshotและ QuerySnapshot โดยใช้ toJSON ได้เช่นเดียวกับประเภทข้อมูล Firestore อย่างไรก็ตาม หากต้องการยกเลิกการซีเรียลไลซ์ คุณต้องใช้ฟังก์ชันแบบสแตนด์อโลน documentSnapshotFromJSON และ querySnapshotFromJSON แทนเมธอด fromJSON แบบคงที่

ตัวอย่างเช่น ผลลัพธ์ querySnapshot ของการดำเนินการ query สามารถ ทำให้เป็นอนุกรมได้โดยใช้วิธี 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();

จากนั้นจะยกเลิกการซีเรียลไลซ์ข้อมูลนี้ได้

import {
  querySnapshotFromJSON
} from 'firebase/firestore';

// deserializedSnapshot is an object of type QuerySnapshot:

const deserializedSnapshot =
  querySnapshotFromJSON(firestore, querySnapshotJson);

ผู้ฟังที่มีสแนปชอตแบบอนุกรม

แม้ว่าข้อมูลที่ค้นหาในระยะ SSR จะมีประโยชน์สำหรับการแสดงผล CSR เริ่มต้น แต่คุณอาจยังต้องตรวจสอบบริการ Firestore เพื่อดูข้อมูลอัปเดตแบบเรียลไทม์

หากแอปของคุณต้องการการอัปเดตแบบเรียลไทม์เหล่านี้ คุณสามารถใช้ฟังก์ชัน onSnapshotResume เพื่อเริ่มต้น SnapshotListenerFirestore ด้วยข้อมูล Snapshot ที่แปลงเป็นอนุกรมแล้ว เช่น

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