Skip to content

Commit e770a15

Browse files
committed
ci: frozen lockfile
1 parent 03b9801 commit e770a15

File tree

4 files changed

+115
-13
lines changed

4 files changed

+115
-13
lines changed

.github/workflows/docs-prs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
${{ runner.os }}-pnpm-store-
4444
4545
- name: Install dependencies
46-
run: pnpm install
46+
run: pnpm install --frozen-lockfile
4747

4848
- name: Build docs
4949
run: pnpm run docs:build

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
${{ runner.os }}-pnpm-store-
4545
4646
- name: Install dependencies
47-
run: pnpm install
47+
run: pnpm install --frozen-lockfile
4848

4949
- name: Build docs
5050
run: pnpm run docs:build

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
${{ runner.os }}-
6161
6262
- name: Install
63-
run: pnpm install
63+
run: pnpm install --frozen-lockfile
6464

6565
- name: Install firebase-tools
6666
run: pnpm add -g firebase-tools

docs/guide/ssr.md

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,121 @@
1-
# Server Side Rendering
1+
# Server Side Rendering (SSR)
22

3-
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
3+
:::tip
4+
If you are using Nuxt.js, read the [Nuxt guide](./nuxt.md) instead, most of the things are already configured for you.
5+
:::
46

5-
## Vue 3 + Suspense
7+
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:
68

7-
By serializing the data on the server (using a store like [Pinia](https://pinia.vuejs.org) for example) and `await`ing the returned `promise` of `useDocument()` you can ensure your data is loaded when rendering the page on the server and hydrating on the client.
9+
<FirebaseExample>
810

911
```vue
10-
<script lang="ts" setup>
11-
const { data: userList, promise } = useCollection(collection(db, 'users'))
12+
<script setup>
13+
import { ref as dbRef } from 'firebase/database'
14+
import { useObject, useDatabase } from 'vuefire'
1215
13-
await promise.value
16+
const db = useDatabase()
17+
// automatically waits for the data to be loaded on the server
18+
const quizResults = useObject(dbRef(db, 'quizzes/' + quizId))
1419
</script>
1520
```
1621

17-
:::tip
18-
Make sure your component is the descendant of [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense.html) in order to use `await` within `<script setup>`.
19-
:::
22+
```vue
23+
<script setup>
24+
import { doc, getDoc } from 'firebase/firestore'
25+
import { useDocument, useFirestore } from 'vuefire'
26+
27+
const db = useFirestore()
28+
// automatically waits for the data to be loaded on the server
29+
const quizResults = useDocument(doc(db, 'quizzes', quizId))
30+
</script>
31+
```
32+
33+
</FirebaseExample>
34+
35+
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](https://github.com/antfu/vitesse):
36+
37+
Add a `src/modules/vuefire.ts` (or `.js`) file:
38+
39+
```ts
40+
// src/modules/vuefire.ts
41+
import { initializeApp } from 'firebase/app'
42+
import { VueFire useSSRInitialState } from 'vuefire'
43+
import type { UserModule } from '~/types'
44+
45+
export const install: UserModule = ({ isClient, initialState, app }) => {
46+
const firebaseApp = initializeApp({
47+
// your config
48+
})
49+
50+
app.use(VueFire, { firebaseApp })
51+
52+
if (isClient) {
53+
// reuse the initial state on the client
54+
useSSRInitialState(initialState.vuefire, firebaseApp)
55+
} else {
56+
// on the server we ensure all the data is retrieved in this object
57+
initialState.vuefire = useSSRInitialState()
58+
}
59+
}
60+
```
61+
62+
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:
63+
64+
```ts
65+
// src/main.ts
66+
import devalue from '@nuxt/devalue'
67+
import { ViteSSG } from 'vite-ssg'
68+
import App from './App.vue'
69+
70+
export const createApp = ViteSSG(
71+
App,
72+
{ routes },
73+
({ app, router, initialState }) => {
74+
// ...
75+
},
76+
{
77+
transformState(state) {
78+
return import.meta.env.SSR ? devalue(state) : state
79+
},
80+
},
81+
)
82+
```
83+
84+
Web Security is a broad topic that we cannot cover here. We recommend you te read these resources to dive deeper:
85+
86+
- [State Serialization in vite-ssg](https://github.com/antfu/vite-ssg#state-serialization)
87+
- [SSR Best practices for Vue.js](https://vuejs.org/guide/best-practices/security.html#server-side-rendering-ssr)
88+
89+
## Usage outside of components
90+
91+
If you are using VueFire composables outside of components, e.g. using `useDocument()` within a [Pinia](https://pinia.vuejs.org) store, you need to manually wait for the data to be loaded on the server as VueFire cannot call `onServerPrefetch()` for you. This means you also need to [use `<Suspense>`](https://vuejs.org/guide/built-ins/suspense.html#suspense) to be able to use `await` within `setup()`. VueFire exposes a function to retrieve all pending promises created by the different composables (`useDocument()`, `useObject()`, etc). Await it inside of **any component that uses the data**:
92+
93+
```vue
94+
<script setup>
95+
import { useQuizStore } from '~/stores/quiz'
96+
import { usePendingPromises } from 'vuefire'
97+
98+
// this store internally calls `useDocument()` when created
99+
const quizStore = useQuizStore()
100+
101+
// since `useDocument()` has been called
102+
await usePendingPromises()
103+
</script>
104+
```
105+
106+
## Exclude from hydration
107+
108+
You can exclude data from hydration by passing `false` to the `ssrKey` option:
109+
110+
```ts
111+
useDocument(..., { ssrKey: false })
112+
useList(..., { ssrKey: false })
113+
// etc
114+
```
115+
116+
<!-- TODO: I wonder if we could attach effect scopes to applications so `onServerPrefetch()` is still awaited when attached -->
117+
118+
<!--
20119
21120
## Vue Router Data Loaders
22121
@@ -26,6 +125,7 @@ Get the data once only on server
26125
<script lang="ts">
27126
export const useUserList = defineLoader(async () => {
28127
const { data: users, promise } = useCollection(collection(db, 'users'), { once: true })
128+
await promise.value
29129
// or
30130
// const users = await useCollectionOnce(collection(db, 'users'))
31131
return users
@@ -36,3 +136,5 @@ export const useUserList = defineLoader(async () => {
36136
const { data: users } = useUserList()
37137
</script>
38138
```
139+
140+
-->

0 commit comments

Comments
 (0)