Skip to content

Commit 33ca00b

Browse files
feat: SSR support (#30)
* feat: expose hydration methods * docs: nuxt example * feat: expose hydration utilities as a separate entry * feat: nuxt hooks * fix: avoid client refetch * fix: data rendered on server * docs: ssr and nuxt documentation * refactor: clean nuxt package.json * style: prettier * docs: mark experimental features in readme * test: nuxt hooks tests
1 parent f972b02 commit 33ca00b

25 files changed

+747
-7
lines changed

.codesandbox/ci.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
22
"packages": ["./"],
3-
"sandboxes": ["/examples/base", "/examples/basic-vue-2.x"],
3+
"sandboxes": [
4+
"/examples/base",
5+
"/examples/basic-vue-2.x",
6+
"/examples/nuxt-simple"
7+
],
48
"node": "14"
59
}

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
"no-console": "warn",
1818
"no-debugger": "warn"
1919
}
20-
}
20+
}

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ If you want to suggest a feature, [create an issue](https://github.com/DamianOsi
1515
## Development
1616

1717
While contributing, make sure to follow the guidelines:
18+
1819
- run `npm run verify` before opening a PR
1920
- write tests for any new piece of code that you are adding to the repository when applicable
2021

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Support for Vue 2.x via [vue-demi](https://github.com/vueuse/vue-demi)
1414
Based on [react-query](https://github.com/tannerlinsley/react-query)
1515

1616
# Documentation
17+
1718
Visit https://vue-query.vercel.app
1819

1920
# Quick Features
@@ -26,7 +27,8 @@ Visit https://vue-query.vercel.app
2627
- Paginated + Cursor-based Queries
2728
- Load-More + Infinite Scroll Queries w/ Scroll Recovery
2829
- Request Cancellation
29-
- [Suspense](https://v3.vuejs.org/guide/migration/suspense.html#introduction) + Fetch-As-You-Render Query Prefetching
30+
- (experimental) [Suspense](https://v3.vuejs.org/guide/migration/suspense.html#introduction) + Fetch-As-You-Render Query Prefetching
31+
- (experimental) SSR support
3032
- Dedicated Devtools
3133
- [![npm bundle size](https://img.shields.io/bundlephobia/minzip/vue-query)](https://bundlephobia.com/result?p=vue-query) (depending on features imported)
3234

docs/_sidebar.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,10 @@
1010
- [Simple](examples/simple.md)
1111
- [Basic](examples/base.md)
1212
- [Multi Page](examples/multi-page.md)
13-
- [Suspense](examples/suspense.md)
14-
- [Vue 2.x](examples/vue-2.x.md)
13+
- [Suspense (experimental)](examples/suspense.md)
14+
- [Vue 2.x (experimental)](examples/vue-2.x.md)
15+
- [Nuxt.js (experimental)](examples/nuxt.md)
16+
17+
- Guides & Concepts
18+
19+
- [SSR & Nuxt.js (experimental)](guides/ssr.md)

docs/examples/nuxt.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- [Open in Codesandbox](https://codesandbox.io/s/github/DamianOsipiuk/vue-query/tree/main/examples/nuxt-simple)
2+
- [View Source](https://github.com/DamianOsipiuk/vue-query/tree/main/examples/nuxt-simple)
3+
4+
<iframe src="https://codesandbox.io/embed/github/DamianOsipiuk/vue-query/tree/main/examples/nuxt-simple?hidenavigation=1&view=preview&codemirror=1"
5+
style="height:700px; border:0; overflow:hidden;"
6+
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
7+
></iframe>

docs/guides/ssr.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
Vue Query supports two ways of prefetching data on the server and passing that to the queryClient.
2+
3+
- Prefetch the data yourself and pass it in as `initialData`
4+
- Quick to set up for simple cases
5+
- Has some caveats
6+
- Prefetch the query on the server, dehydrate the cache and rehydrate it on the client
7+
- Requires slightly more setup up front
8+
9+
## Using Nuxt.js
10+
11+
### Using Hydration
12+
13+
Vue Query supports prefetching multiple queries on the server in Nuxt.js and then _dehydrating_ those queries to the queryClient. This means the server can prerender markup that is immediately available on page load and as soon as JS is available, Vue Query can upgrade or _hydrate_ those queries with the full functionality of the library. This includes refetching those queries on the client if they have become stale since the time they were rendered on the server.
14+
15+
To support caching queries on the server and set up hydration:
16+
17+
- Use `useNuxtQueryProvider` inside setup function of your layout component
18+
19+
```js
20+
// layouts/default.vue
21+
<template>
22+
<div>
23+
<nuxt />
24+
</div>
25+
</template>
26+
27+
<script>
28+
import { defineComponent } from "@nuxtjs/composition-api";
29+
import { useNuxtQueryProvider } from "vue-query/ssr";
30+
31+
export default defineComponent({
32+
setup() {
33+
useNuxtQueryProvider();
34+
},
35+
});
36+
</script>
37+
38+
```
39+
40+
Now you are ready to prefetch some data in your pages with `onServerPrefetch`.
41+
42+
- Use `useQueryClient` to get server-side instance of queryClient
43+
- Use `useContext` to get nuxt context
44+
- Prefetch all the queries that you need with `prefetchQuery`
45+
- Use `useNuxtDehydrate` to dehydrate the query cache and pass it to the client-side via nuxt context.
46+
47+
```js
48+
// pages/todos.vue
49+
<template>
50+
<div>
51+
<button @click="refetch">Refetch</button>
52+
<p>{{ data }}</p>
53+
</div>
54+
</template>
55+
56+
<script lang="ts">
57+
import {
58+
defineComponent,
59+
onServerPrefetch,
60+
useContext,
61+
} from "@nuxtjs/composition-api";
62+
import { useQuery, useQueryClient } from "vue-query";
63+
import { useNuxtDehydrate } from "vue-query/ssr";
64+
65+
export default defineComponent({
66+
setup() {
67+
// This will be prefetched and sent from the server
68+
const { refetch, data } = useQuery("todos", getTodos);
69+
// This won't be prefetched, it will start fetching on client side
70+
const { data2 } = useQuery("todos2", getTodos);
71+
72+
onServerPrefetch(async () => {
73+
const { ssrContext } = useContext();
74+
const queryClient = useQueryClient();
75+
await queryClient.prefetchQuery("todos", getTodos);
76+
77+
useNuxtDehydrate(ssrContext, queryClient);
78+
});
79+
80+
return {
81+
refetch,
82+
data,
83+
};
84+
},
85+
});
86+
</script>
87+
```
88+
89+
As demonstrated, it's fine to prefetch some queries and let others fetch on the queryClient. This means you can control what content server renders or not by adding or removing `prefetchQuery` for a specific query.
90+
91+
## Tips, Tricks and Caveats
92+
93+
### Only successful queries are included in dehydration
94+
95+
Any query with an error is automatically excluded from dehydration. This means that the default behaviour is to pretend these queries were never loaded on the server, usually showing a loading state instead, and retrying the queries on the queryClient. This happens regardless of error.
96+
97+
Sometimes this behavior is not desirable, maybe you want to render an error page with a correct status code instead on certain errors or queries. In those cases, use `fetchQuery` and catch any errors to handle those manually.
98+
99+
### Staleness is measured from when the query was fetched on the server
100+
101+
A query is considered stale depending on when it was `dataUpdatedAt`. A caveat here is that the server needs to have the correct time for this to work properly, but UTC time is used, so timezones do not factor into this.
102+
103+
Because `staleTime` defaults to `0`, queries will be refetched in the background on page load by default. You might want to use a higher `staleTime` to avoid this double fetching, especially if you don't cache your markup.
104+
105+
This refetching of stale queries is a perfect match when caching markup in a CDN! You can set the cache time of the page itself decently high to avoid having to re-render pages on the server, but configure the `staleTime` of the queries lower to make sure data is refetched in the background as soon as a user visits the page. Maybe you want to cache the pages for a week, but refetch the data automatically on page load if it's older than a day?

examples/nuxt-simple/.eslintrc.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
browser: true,
5+
node: true,
6+
},
7+
parserOptions: {
8+
parser: "babel-eslint",
9+
},
10+
extends: ["plugin:vue/essential"],
11+
plugins: ["vue"],
12+
rules: {},
13+
};

examples/nuxt-simple/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
npm-debug.log
3+
.nuxt
4+
dist
5+
yarn.lock

examples/nuxt-simple/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# my-nuxt-project
2+
3+
> Nuxt.js project
4+
5+
## Build Setup
6+
7+
```bash
8+
# install dependencies
9+
$ npm install # Or yarn install
10+
11+
# serve with hot reload at localhost:3000
12+
$ npm run dev
13+
14+
# build for production and launch server
15+
$ npm run build
16+
$ npm start
17+
18+
# generate static project
19+
$ npm run generate
20+
```
21+
22+
For detailed explanation on how things work, checkout the [Nuxt.js docs](https://github.com/nuxt/nuxt.js).

0 commit comments

Comments
 (0)