Server Side Rendering (SSR)
TIP
Manually doing Server Side Rendering can get really complex, it is recommended to use Nuxt. Read the Nuxt guide, most of the things are already configured for you.
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:
<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:
// 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 TimeStamp
and GeoPoint
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:
// src/main.ts
// https://github.com/Rich-Harris/devalue#usage
import devalue from 'devalue'
import { ViteSSG } from 'vite-ssg'
import App from './App.vue'
import {
devalueCustomParsers,
devalueCustomStringifiers,
} from 'vuefire'
export const createApp = ViteSSG(
App,
{ routes },
({ app, router, initialState }) => {
// ...
},
{
transformState(state) {
return import.meta.env.SSR
? devalue.stringify(state, devalueCustomStringifiers)
: devalue.parse(state, devalueCustomParsers)
},
}
)
TIP
This is handled out of the box with the nuxt-vuefire
plugin in Nuxt projects.
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:
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:
<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()
:
<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>