ใช้ 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 ที่สร้างในไคลเอ็นต์

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

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

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

การทราบเวลาที่ควรเรียกใช้ deleteApp ในอินสแตนซ์ FirebaseServerApp อาจเป็นเรื่องยาก โดยเฉพาะอย่างยิ่งหากคุณดำเนินการแบบอะซิงโครนัสหลายรายการแบบขนาน ช่อง releaseOnDeref ของ FirebaseServerAppSettings ช่วยให้การดำเนินการนี้ง่ายขึ้น หากคุณกำหนดการอ้างอิงออบเจ็กต์ที่มีอายุการใช้งานตามขอบเขตของคำขอ (เช่น ออบเจ็กต์ส่วนหัวของคำขอ SSR) ให้กับ releaseOnDeref 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 ได้ในไคลเอ็นต์ ซึ่งจะช่วยให้เซสชันดำเนินการต่อในฝั่งเซิร์ฟเวอร์ แม้แต่เมธอดการลงชื่อเข้าใช้ที่ต้องมีการโต้ตอบของผู้ใช้ นอกจากนี้ ยังช่วยให้สามารถย้ายการดำเนินการที่ใช้ทรัพยากรมากไปยังเซิร์ฟเวอร์ เช่น การค้นหา 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 ได้ที่ การจัดการเซสชันด้วย Service Worker นอกจากนี้ โปรดดูการเปลี่ยนแปลง ฝั่งเซิร์ฟเวอร์สำหรับโค้ด ที่แสดงวิธีแยกวิเคราะห์โทเค็นเหล่านี้จากส่วนหัวเพื่อใช้ในการ FirebaseServerAppเริ่มต้น

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

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

ส่วนนี้จะอธิบายวิธีนำข้อมูลที่ดึงมาในระยะการแสดงผลฝั่งเซิร์ฟเวอร์ (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);

Listener ที่มีสแนปชอตที่ซีเรียลไลซ์แล้ว

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

หากแอปต้องมีการอัปเดตแบบเรียลไทม์เหล่านี้ คุณสามารถใช้onSnapshotResume ฟังก์ชันเพื่อเริ่มต้น SnapshotListener ของ Firestore ด้วยข้อมูล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);