Skip to content
On this page

Server Side Rendering (SSR)

WARNING

SSR support is still experimental. Please report any issues you find.

SSR with Vitesse

When doing SSR (Server Side Rendering) you want to wait for the data on the server to serialize it and retrieve it on the client side where it will displayed. VueFire already waits for the data for you if you use the composables within components:

vue
<script setup>
import { doc, getDoc } from 'firebase/firestore'
import { useDocument, useFirestore } from 'vuefire'

const db = useFirestore()
// automatically waits for the data to be loaded on the server
const quizResults = useDocument(doc(db, 'quizzes', quizId))
</script>

You only need to escape and serialize the data to the client and handle state hydration. This depends on what you are using to do SSR but should look similar to this example using the Vitesse template:

Add a src/modules/vuefire.ts (or .js) file:

ts
// src/modules/vuefire.ts
import { initializeApp } from 'firebase/app'
import { VueFire useSSRInitialState } from 'vuefire'
import type { UserModule } from '~/types'

export const install: UserModule = ({ isClient, initialState, app }) => {
  const firebaseApp = initializeApp({
    // your config
  })

  app.use(VueFire, { firebaseApp })

  if (isClient) {
    // reuse the initial state on the client
    useSSRInitialState(initialState.vuefire, firebaseApp)
  } else {
    // on the server we ensure all the data is retrieved in this object
    initialState.vuefire = useSSRInitialState(
      // let `useSSRInitialState` create the initial object for us
      undefined,
      // this is necessary to work with concurrent requests
      firebaseApp,
    )
  }
}

Note that by default, vite-ssg (used by Vitesse) uses JSON.stringify() to serialize the state, which is faster but doesn't support some values like Date objects and also exposes your application to some attacks if your data comes from the user. You can use a custom transformState function to handle this:

ts
// src/main.ts
import devalue from '@nuxt/devalue'
import { ViteSSG } from 'vite-ssg'
import App from './App.vue'

export const createApp = ViteSSG(
  App,
  { routes },
  ({ app, router, initialState }) => {
    // ...
  },
  {
    transformState(state) {
      return import.meta.env.SSR ? devalue(state) : state
    },
  },
)

Web Security is a broad topic that we cannot cover here. We recommend you to read these resources to dive deeper:

Manual SSR keys

VueFire automatically infers an SSR key based on the path of the document or collection whenever possible. This means there are some scenarios where you have to provide a manual ssrKey:

  • When using Firestore Queries
  • When binding the same document multiple times

In these scenarios, provide the ssrKey as a second argument to useDocument(), useCollection(), etc:

ts
useCollection(queryRef, { ssrKey: 'my-quiz' })

Usage outside of components

If you are using VueFire composables outside of components, e.g. using useDocument() within a Pinia store, you need to manually wait for the data to be loaded on the server as VueFire cannot call onServerPrefetch() for you and you will have to manually call it yourself. VueFire exposes a function to retrieve all pending promises created by the different composables (useDocument(), useDatabaseObject(), etc). You will need to use it inside of any component that uses the data:

vue
<script setup>
import { useQuizStore } from '~/stores/quiz'
import { usePendingPromises } from 'vuefire'

// this store internally calls `useDocument()` when created
const quizStore = useQuizStore()

// `useDocument()` has been called within `useQuizStore()` but this component isn't aware of it
onServerPrefetch(() => usePendingPromises())
</script>

While the recommended approach is to use onServerPrefetch(), another possibility is to use <Suspense> to be able to use await within setup():

vue
<script setup>
import { useQuizStore } from '~/stores/quiz'
import { usePendingPromises } from 'vuefire'

// this store internally calls `useDocument()` when created
const quizStore = useQuizStore()

// since `useDocument()` has been called 
await usePendingPromises()
</script>

Exclude from hydration

You can exclude data from hydration by passing false to the ssrKey option:

ts
useDocument(..., { ssrKey: false })
useDatabaseList(..., { ssrKey: false })
// etc

Released under the MIT License.