Utilizzare Firebase nelle app web dinamiche con il rendering lato server (SSR)

Se hai lavorato con l'SDK Firebase JS o altri SDK client Firebase, probabilmente conosci l'interfaccia FirebaseApp e come utilizzarla per configurare le istanze dell'app. Per facilitare operazioni simili sul lato server, Firebase fornisce FirebaseServerApp.

FirebaseServerApp è una variante di FirebaseApp da utilizzare negli ambienti di rendering lato server (SSR). Include strumenti per continuare le sessioni Firebase che si estendono oltre la divisione tra rendering lato client (CSR) e rendering lato server. Questi strumenti e queste strategie possono contribuire a migliorare le app web dinamiche create con Firebase e sottoposte a deployment in ambienti Google come Firebase App Hosting.

Utilizza FirebaseServerApp per:

  • Eseguire codice lato server nel contesto utente, a differenza dell'SDK Firebase Admin che dispone di diritti di amministrazione completi.
  • Consentire l'utilizzo di App Check negli ambienti SSR.
  • Continuare una sessione di Firebase Authentication creata nel client.

Il ciclo di vita di FirebaseServerApp

I framework di rendering lato server (SSR) e altri runtime non browser come i worker cloud ottimizzano il tempo di inizializzazione riutilizzando le risorse in più esecuzioni. FirebaseServerApp è progettato per adattarsi a questi ambienti utilizzando un meccanismo di conteggio dei riferimenti. Se un'app richiama initializeServerApp con gli stessi parametri di un precedente initializeServerApp, riceve la stessa istanza FirebaseServerApp già inizializzata. In questo modo si riducono l'overhead di inizializzazione e le allocazioni di memoria non necessari. Quando viene richiamato deleteApp su un'istanza FirebaseServerApp, il conteggio dei riferimenti viene ridotto e l'istanza viene liberata dopo che il conteggio dei riferimenti raggiunge lo zero.

Pulizia delle istanze FirebaseServerApp

Può essere difficile sapere quando chiamare deleteApp su un'istanza FirebaseServerApp, soprattutto se esegui molte operazioni asincrone in parallelo. Il campo releaseOnDeref di FirebaseServerAppSettings contribuisce a semplificare questa operazione. Se assegni a releaseOnDeref un riferimento a un oggetto con la durata dell'ambito della richiesta (ad esempio, l'oggetto delle intestazioni della richiesta SSR), FirebaseServerApp ridurrà il conteggio dei riferimenti quando il framework recupera l'oggetto delle intestazioni. In questo modo, l'istanza FirebaseServerApp viene pulita automaticamente.

Ecco un esempio di utilizzo di 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);
  ...
}

Riprendere le sessioni autenticate create sul client

Quando un'istanza di FirebaseServerApp viene inizializzata con un token ID Auth, consente di collegare le sessioni utente autenticate tra gli ambienti di rendering lato client (CSR) e rendering lato server (SSR). Le istanze dell'SDK Firebase Authentication inizializzate con un oggetto FirebaseServerApp contenente un token ID Auth tenteranno di eseguire l'accesso dell'utente all'inizializzazione senza che l'applicazione debba richiamare alcun metodo di accesso.

La fornitura di un token ID Auth consente alle app di utilizzare qualsiasi metodo di accesso di Auth sul client, garantendo che la sessione continui sul lato server, anche per i metodi di accesso che richiedono l'interazione dell'utente. Inoltre, consente di scaricare operazioni intensive sul server, come le query Firestore autenticate, il che dovrebbe migliorare il rendimento del rendering dell'app.

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

Utilizzare App Check negli ambienti SSR

L'applicazione di App Check si basa su un'istanza dell'SDK App Check che gli SDK Firebase utilizzano per chiamare internamente getToken. Il token risultante viene quindi incluso nelle richieste a tutti i servizi Firebase, consentendo al backend di convalidare l'app.

Tuttavia, poiché l'SDK App Check ha bisogno di un browser per accedere a euristiche specifiche per la convalida dell'app, non può essere inizializzato negli ambienti server.

FirebaseServerApp fornisce un'alternativa. Se viene fornito un token App Check generato dal client durante l'inizializzazione di FirebaseServerApp, verrà utilizzato dagli SDK dei prodotti Firebase quando vengono richiamati i servizi Firebase, eliminando la necessità di un'istanza dell'SDK App Check.

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

Passare i token client alla fase di rendering lato server

Per trasmettere i token ID Auth autenticati (e i token App Check) dal client alla fase di rendering lato server (SSR), utilizza un service worker. Questo approccio prevede l'intercettazione delle richieste di recupero che attivano SSR e l'aggiunta dei token alle intestazioni delle richieste.

Consulta Gestione delle sessioni con i service worker per un'implementazione di riferimento di un service worker di Firebase Authentication. Consulta anche Modifiche lato server per il codice che mostra come analizzare questi token dalle intestazioni per l'utilizzo nell' FirebaseServerApp inizializzazione.

Utilizzare Firestore negli ambienti SSR

Quando crei applicazioni web con il rendering lato server (SSR), spesso devi condividere i dati tra il server e il client per ottimizzare il rendimento e l'esperienza utente. L'SDK Firestore fornisce strumenti di serializzazione che ti consentono di acquisire snapshot e tipi di dati specifici sul server e di passarli direttamente ai componenti lato client. Questo processo elimina i recuperi ridondanti consentendo al client di eseguire l'idratazione dello stato utilizzando i dati precaricati durante la fase SSR. Inoltre, puoi passare da questi stati serializzati ai listener in tempo reale, assicurandoti che l'applicazione rimanga sincronizzata con il database.

Questa sezione descrive come riutilizzare i dati recuperati durante la fase di rendering lato server (SSR) all'interno dei componenti lato client.

Serializzare i tipi di dati

Alcuni tipi di dati Firestore forniscono un metodo toJSON per convertire i dati in un formato serializzabile. Questi includono istanze di oggetti come Bytes, GeoPoint, Timestamp e VectorValue.

Una volta che i dati sono in formato JSON, puoi passarli dal server al client tramite i meccanismi standard del framework o come parametri ai componenti che si estendono oltre la divisione. Ad esempio:

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

Deserializzare i tipi di dati

I tipi di dati Firestore includono il metodo statico fromJSON per convertire i dati serializzati in un tipo di dati Firestore utilizzabile.

Ad esempio, il seguente deserializza un tipo di dati Bytes:

import {
  Bytes
} from 'firebase/firestore';

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

Serializzare e deserializzare gli snapshot Firestore

Analogamente ai tipi di dati Firestore, puoi serializzare le istanze di DocumentSnapshot e QuerySnapshot utilizzando toJSON. Tuttavia, per deserializzarli, devi utilizzare le funzioni autonome documentSnapshotFromJSON e querySnapshotFromJSON anziché un metodo statico fromJSON.

Ad esempio, i risultati querySnapshot di un'operazione query possono essere serializzati utilizzando il metodo 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();

Quindi, questi dati possono essere deserializzati:

import {
  querySnapshotFromJSON
} from 'firebase/firestore';

// deserializedSnapshot is an object of type QuerySnapshot:

const deserializedSnapshot =
  querySnapshotFromJSON(firestore, querySnapshotJson);

Listener con snapshot serializzati

Sebbene i dati sottoposti a query durante la fase SSR siano utili per il rendering CSR iniziale, potresti comunque dover monitorare il servizio Firestore per gli aggiornamenti in tempo reale di queste informazioni.

Se la tua app richiede questi aggiornamenti in tempo reale, puoi utilizzare la onSnapshotResume funzione per inizializzare i SnapshotListener di Firestore con i dati Snapshot serializzati. Ad esempio:

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