Используйте Firebase в динамических веб-приложениях с SSR (серверный рендеринг), используйте Firebase в динамических веб-приложениях с SSR (серверный рендеринг)

Если вы работали с Firebase JS SDK или другими клиентскими SDK Firebase, вы, вероятно, знакомы с интерфейсом FirebaseApp и тем, как использовать его для настройки экземпляров приложений. Для упрощения аналогичных операций на стороне сервера Firebase предоставляет FirebaseServerApp .

FirebaseServerApp — это вариант FirebaseApp , предназначенный для использования в средах серверного рендеринга (SSR). Он включает инструменты для продолжения сессий Firebase, охватывающих как клиентский, так и серверный рендеринг. Эти инструменты и стратегии могут помочь улучшить динамические веб-приложения, созданные с помощью Firebase и развернутые в средах Google, таких как Firebase App Hosting .

Используйте FirebaseServerApp для:

  • В отличие от Firebase Admin SDK, который предоставляет полные права администратора, серверный код выполняется в контексте пользователя .
  • Включите использование App Check в средах SSR.
  • Продолжить сессию аутентификации Firebase, созданную на стороне клиента.

Жизненный цикл FirebaseServerApp

Фреймворки серверного рендеринга (SSR) и другие среды выполнения, не использующие браузеры, такие как облачные рабочие процессы, оптимизируют время инициализации за счет повторного использования ресурсов в нескольких выполнениях. 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 инициализируется токеном Auth ID, это обеспечивает связь между аутентифицированными пользовательскими сессиями в средах клиентского рендеринга (CSR) и серверного рендеринга (SSR). Экземпляры Firebase Auth SDK, инициализированные объектом FirebaseServerApp , содержащим токен Auth ID, попытаются выполнить вход пользователя при инициализации без необходимости вызова каких-либо методов входа в систему со стороны приложения.

Предоставление токена Auth ID позволяет приложениям использовать любой из методов авторизации 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.

Используйте проверку приложений в средах SSR.

Проверка приложений с помощью App Check основана на использовании экземпляра SDK App Check, который SDK Firebase используют для внутреннего вызова getToken . Полученный токен затем включается в запросы ко всем сервисам Firebase, что позволяет бэкэнду проверять приложение.

Однако, поскольку для доступа к определенным эвристическим алгоритмам проверки приложений App Check SDK требуется браузер, его нельзя инициализировать в серверной среде.

FirebaseServerApp предлагает альтернативный вариант. Если во время инициализации FirebaseServerApp предоставляется сгенерированный клиентом токен App Check, он будет использоваться SDK продуктов Firebase при вызове сервисов Firebase, что устраняет необходимость в экземпляре 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.

Передайте клиентские токены на этап рендеринга на стороне сервера.

Для передачи аутентифицированных токенов Auth ID (и токенов App Check) от клиента к этапу рендеринга на стороне сервера (SSR) используется сервис-воркер. Этот подход предполагает перехват запросов fetch, запускающих SSR, и добавление токенов в заголовки запроса.

Для получения примера реализации сервис-воркера Firebase Auth обратитесь к разделу «Управление сессиями с помощью сервис-воркеров» . Также см. раздел «Изменения на стороне сервера» для кода, демонстрирующего, как извлекать эти токены из заголовков для использования при инициализации FirebaseServerApp .

Используйте Firestore в средах SSR.

При разработке веб-приложений с использованием серверного рендеринга (SSR) часто возникает необходимость обмена данными между сервером и клиентом для оптимизации производительности и удобства использования. SDK Firestore предоставляет инструменты сериализации, позволяющие получать снимки состояния и определенные типы данных на сервере и передавать их непосредственно клиентским компонентам. Этот процесс исключает избыточные запросы, позволяя клиенту восстанавливать состояние, используя данные, предварительно полученные на этапе 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

Аналогично типам данных Firestore, вы можете сериализовать экземпляры DocumentSnapshot и QuerySnapshot с помощью toJSON . Однако для их десериализации необходимо использовать отдельные функции documentSnapshotFromJSON и querySnapshotFromJSON вместо статического метода fromJSON .

Например, результаты query querySnapshot можно сериализовать с помощью метода 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 для инициализации объектов Firestore SnapshotListener сериализованными данными 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);