Use Firebase in dynamic web apps with SSR (Server-side Rendering)

If you have worked with the Firebase JS SDK or other Firebase client SDKs, you are probably familiar with the FirebaseApp interface and how to use it to configure app instances. To facilitate similar operations on the server side, Firebase provides FirebaseServerApp.

FirebaseServerApp is a variant of FirebaseApp for use in server-side rendering (SSR) environments. It includes tools to continue Firebase sessions that span the client side rendering (CSR) / server-side rendering divide. These tools and strategies can help enhance dynamic web apps built with Firebase and deployed in Google environments like Firebase App Hosting.

Use FirebaseServerApp to:

  • Execute server-side code within the user context, in contrast to the Firebase Admin SDK which has full administration rights.
  • Enable the use of App Check in SSR environments.
  • Continue a Firebase Auth session that was created in the client.

The FirebaseServerApp lifecycle

Server-side rendering (SSR) frameworks and other non-browser runtimes such as cloud workers optimize for initialization time by reusing resources across multiple executions. FirebaseServerApp is designed to accommodate these environments by using a reference count mechanism. If an app invokes initializeServerApp with the same parameters as a previous initializeServerApp, it receives the same FirebaseServerApp instance that was already initialized. This cuts down on unnecessary initialization overhead and memory allocations. When deleteApp is invoked on a FirebaseServerApp instance, it reduces the reference count, and the instance is freed after the reference count reaches zero.

Cleaning up FirebaseServerApp instances

It can be tricky to know when to call deleteApp on a FirebaseServerApp instance, especially if you are running many asynchronous operations in parallel. The releaseOnDeref field of the FirebaseServerAppSettings helps simplify this. If you assign releaseOnDeref a reference to an object with the lifespan of the request's scope (for example, the headers object of the SSR request), the FirebaseServerApp will reduce its reference count when the framework reclaims the header object. This automatically cleans up your FirebaseServerApp instance.

Here's an example usage of releaseOnDeref:

/// Next.js
import { headers } from 'next/headers'
import { FirebaseServerAppSettings, initializeServerApp} from "@firebase/app";

export default async function Page() {
  const headersObj = await headers();
  appSettings.releaseOnDeref = headersObj;
  let appSettings: FirebaseServerAppSettings = {};
  const serverApp = initializeServerApp(firebaseConfig, appSettings);
  ...
}

Resume authenticated sessions created on the client

When an instance of FirebaseServerApp is initialized with an Auth ID token, it enables bridging of authenticated user sessions between the client-side rendering (CSR) and server-side rendering (SSR) environments. Instances of the Firebase Auth SDK initialized with a FirebaseServerApp object containing an Auth ID token will attempt to sign in the user on initialization without the need for the application to invoke any sign-in methods.

Providing an Auth ID token allows apps to use any of Auth's sign-in methods on the client, ensuring that the session continues on the server-side, even for those sign-in methods that require user interaction. Additionally, it enables the offloading of intensive operations to the server such as authenticated Firestore queries, which should improve your app's rendering performance.

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

Use App Check in SSR environments

App Check enforcement relies on an App Check SDK instance that Firebase SDKs use to internally call getToken. The resulting token is then included in requests to all Firebase services, allowing the backend to validate the app.

However, because the App Check SDK needs a browser to access specific heuristics for app validation, it can't be initialized in server environments.

FirebaseServerApp provides an alternative. If a client-generated App Check token is provided during FirebaseServerApp initialization, it will be used by the Firebase product SDKs when invoking Firebase services, eliminating the need for an App Check SDK instance.

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

Pass client tokens to the server-side rendering phase

To transmit authenticated Auth ID tokens (and App Check tokens) from the client to the server-side rendering (SSR) phase, use a service worker. This approach involves intercepting fetch requests that trigger SSR and appending the tokens to the request headers.

Refer to Session management with service workers for a reference implementation of a Firebase Auth service worker. Also see Server side changes for code that demonstrates how to parse these tokens from the headers for use in FirebaseServerApp initialization.