ใช้ 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 ระบบจะลดจำนวนการอ้างอิง และอินสแตนซ์จะถูกปล่อยหลังจากจำนวนการอ้างอิงถึงศูนย์

กำลังล้างอินสแตนซ์ 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 ที่มีโทเค็นรหัสการตรวจสอบสิทธิ์จะพยายามลงชื่อเข้าใช้ผู้ใช้เมื่อเริ่มต้นโดยที่แอปพลิเคชันไม่จำเป็นต้องเรียกใช้วิธีการลงชื่อเข้าใช้ใดๆ

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

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

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 วิธีนี้เกี่ยวข้องกับการสกัดกั้นคำขอดึงข้อมูลที่ทริกเกอร์ SSR และต่อท้ายโทเค็นกับส่วนหัวของคำขอ

ดูการจัดการเซสชันด้วย Service Worker เพื่อดูตัวอย่างการติดตั้งใช้งาน Service Worker ของ Firebase Auth นอกจากนี้ โปรดดูการเปลี่ยนแปลงฝั่งเซิร์ฟเวอร์สำหรับโค้ดที่สาธิตวิธีแยกวิเคราะห์โทเค็นเหล่านี้จากส่วนหัวเพื่อใช้ในการเริ่มต้น 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);

ผู้ฟังที่มีภาพรวมแบบเรียงอันดับ

แม้ว่าข้อมูลที่ค้นหาในระหว่างระยะ 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);