Next JS
Quick Start

The following quick start example builds upon the burrito example in the reactfire quick start guide (opens in a new tab).

If you have not done so already, follow those instructions and we will pick up where it leaves off.

Concepts

Before we start coding, let's review some concepts.

The concept of how we will manage Server Side Rendering (SSR) is based (and heavily inspired ❤️) by the tanstack query (opens in a new tab) model for SSR.

Hydration

We will be Hydrating the reactfire data with pre fetched data which we dehydrate from the server.

This simply means we will be using getServerSideProps to fetch the data on the server and pass it to reactfire as the initial data for queries.

The outcome is that there is no initial loading state for the data on the client, with the data available immediately, to then be updated with reactfire as the data changes in firestore.

Key points to note

  • Be careful not to use firestore and firebase-admin in the wrong place
    • firestore is for the client
    • firebase-admin is for the server (getServerSideProps)

Hydrate component

reactfire-ssr provides a Hydrate component which is responsible for passing through any dehydrated data to your nextjs pages.

Add this component to your _app.tsx file.

Ensure it is a child of the FirestoreProvider component from reactfire.

_app.tsx
import { Hydrate } from 'reactfire-ssr'
import {getFirestore} from "firebase/firestore";
import {
  FirestoreProvider,
  useFirebaseApp,
} from "reactfire";
 
 export default function MyApp({ Component, pageProps }) {
   const sdk = getFirestore(useFirebaseApp());
   return (
     <FirestoreProvider sdk={sdk}>
	   <Hydrate state={pageProps.dehydratedState}>
	     <Component {...pageProps} />
	   </Hydrate>
     </FirestoreProvider>
   )
 }

Pre fetching data

Now we need to use getServerSideProps to pre-fetch the data from firestore.

Because getServerSideProps runs on the server we need to use the firebase-admin sdk to access firestore not firebase.

First initialize initReactfireSSR which is the base object which all exposed methods you need are attached to (this also helps with type hinting).

This function accepts a generic type which you should populate with unique identifiers for each DocumentReference and CollectionReference you will be using.

We aim to remove the need for this in the future, but for now until we can figure out a better way to do this, this is the best solution.

The unique identifiers can be anything you want, you will pass them to the hooks when you access the data on the client.

⚠️

Only call initializeReactfireSSR once in your application. Its a good idea to do this in a separate file and import it where you need it.

pages/index.tsx
import {initializeReactfireSSR} from "reactfire-ssr";
 
const reactfireSSR =
  initializeReactfireSSR<
    [
      "burritoDocument",
      "tryreactfireCollection",
      "tryreactfireCollectionYummy"
    ]
  >();

Use the dehydrate method function which is a helper to unwrap a list of DocumentReference and CollectionReference objects, pass this as props to the page to make it available to reactfire-ssr hooks in your components.

You will notice the keys in the objects passed to dehydrate match the unique identifiers you passed to initializeReactfireSSR.

pages/index.tsx
import {getFirestore} from "firebase-admin";
import {initializeReactfireSSR} from "reactfire-ssr";
/**
* Ensure you have initialized firebase somewhere,
* see the examples directory in the repository for an example
*/
const firestore = getFirestore()
const reactfireSSR =
  initializeReactfireSSR<
    [
      "burritoDocument",
      "tryreactfireCollection",
      "tryreactfireCollectionYummy"
    ]
  >();
 
export async function getServerSideProps() {
  const burritoDocRef = firestore
    .doc("tryreactfire/burrito")
    .withConverter(burritoConverterAdmin); // This converter is optional, but it provides better type safety if you provide one
  const tryreactfireCollectionRef = firestore
    .collection("tryreactfire")
    .withConverter(burritoConverterAdmin);
 
  const tryreactfireCollectionRefYummy = tryreactfireCollectionRef.where(
    "yummy",
    "==",
    true
  );
  const dehydrateData = await reactfireSSR.dehydrate(
    {
      burritoDocument: burritoDocRef,
      burritoDocument2: burritoDocRef,
    },
    {
      tryreactfireCollection: tryreactfireCollectionRef,
      tryreactfireCollectionYummy: tryreactfireCollectionRefYummy,
    }
  );
  return {
    props: dehydrateData,
	/**
	* Or pass other props alongside the dehydrated data
	*/
    // props: { ...dehydrateData, myOtherProp: "goesHere" }
  };
}

Using Pre fetched data

Now we can use the useHydratedFirestoreDocData and useHydratedFirestoreCollectionData hooks to access the pre-fetched data.

The first argument is the same as the useFirestoreDocData and useFirestoreCollectionData hooks which these wrap, the second argument is the unique identifier you passed to initializeReactfireSSR.

Under the hood these hooks will use the dehydrated data if it is available against the unique identifier you passed to the hook.

If it's available, your data will be available instantly on the client without a loading state.

pages/index.tsx
import {reactfireSSR} from "reactfire-ssr";
 
const firestore = getFirestore()
 
export async function getServerSideProps() {
  ...
}
 
export default function Home() {
 const burritoDocRef = doc(
    useFirestore(),
    "tryreactfire",
    "burrito",
  ).withConverter(burritoConverter);
  const collectionRef = collection(useFirestore(), "tryreactfire").withConverter(burritoConverter), // This converter is optional, but it provides better type safety
 
  const burritoDoc = reactfireSSR.useHydratedFirestoreDocData(burritoDocRef, "burritoDocument");
  const tryreactfireCollection = reactfireSSR.useHydratedFirestoreCollectionData(
    collectionRef, "tryreactfireCollection"
  );
 
  if (
    burritoDoc.status === "loading" ||
    tryreactfireCollection.status === "loading"
  ) {
    /**
     * We are prefetching the required data on the server, so this scenario
     * should never happen on the client.
     */
    throw new Error("Should not happen!");
  }
 
  return (
    <p>
      There are {tryreactfireCollection.data.length} document(s) in the
      collection! <br />
      The burrito is {burritoDoc.data.yummy ? "good" : "bad"}!
    </p>
  );
}

Stuck?

If you get stuck there is a working example in the repository (opens in a new tab).

If you are still stuck, please open an issue (opens in a new tab)