Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(core) - Add staleWhileRevalidate option to ssrExchange #1852

Merged
merged 3 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/new-horses-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'next-urql': minor
---

Add new `staleWhileRevalidate` option from the `ssrExchange` addition to `withUrqlClient`'s options. This is useful when Next.js is used in static site generation (SSG) mode.
5 changes: 5 additions & 0 deletions .changeset/spicy-poems-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/core': minor
---

Add a `staleWhileRevalidate` option to the `ssrExchange`, which allows the client to immediately refetch a new result on hydration, which may be used for cached / stale SSR or SSG pages. This is different from using `cache-and-network` by default (which isn't recommended) as the `ssrExchange` typically acts like a "replacement fetch request".
22 changes: 21 additions & 1 deletion docs/advanced/server-side-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const client = createClient({
The `ssrExchange` must be initialized with the `isClient` and `initialState` options. The `isClient`
option tells the exchange whether it's on the server- or client-side. In our example we use `typeof window` to determine this, but in Webpack environments you may also be able to use `process.browser`.

Optionally, we may also choose to enable `staleWhileRevalidate`. When enabled this flag will ensure that although a result may have been rehydrated from our SSR result, another
refetch `network-only` operation will be issued, to update stale data. This is useful for statically generated sites (SSG) that may ship stale data to our application initially.

The `initialState` option should be set to the serialized data you retrieve on your server-side.
This data may be retrieved using methods on `ssrExchange()`. You can retrieve the serialized data
after server-side rendering using `ssr.extractData()`:
Expand Down Expand Up @@ -204,7 +207,7 @@ Optimization"](https://nextjs.org/docs/advanced-features/automatic-static-optimi
// pages/index.js
import React from 'react';
import Head from 'next/head';
import { useQuery } from "urql";
import { useQuery } from 'urql';
import { withUrqlClient } from 'next-urql';

const Index = () => {
Expand Down Expand Up @@ -305,6 +308,23 @@ The above example will make sure the page is rendered as a static-page, it's imp
so in our case we were only interested in getting our todos, if there are child components relying on data you'll have to make
sure these are fetched as well.

### Stale While Revalidate

If we choose to use Next's static site generation (SSG or ISG) we may be embedding data in our initial payload that's stale on the client. In this case, we may want to update this data immediately after rehydration.
We can pass `staleWhileRevalidate: true` to `withUrqlClient`'s second option argument to Switch it to a mode where it'll refresh its rehydrated data immediately by issuing another network request.

```js
export default withUrqlClient(
ssr => ({
url: 'your-url',
}),
{ staleWhileRevalidate: true }
)(...);
```

Now, although on rehydration we'll receive the stale data from our `ssrExchange` first, it'll also immediately issue another `network-only` operation to update the data.
During this revalidation our stale results will be marked using `result.stale`. While this is similar to what we see with `cache-and-network` without server-side rendering, it isn't quite the same. Changing the request policy wouldn't actually refetch our data on rehydration as the `ssrExchange` is simply a replacement of a full network request. Hence, this flag allows us to treat this case separately.

### Resetting the client instance

In rare scenario's you possibly will have to reset the client instance (reset all cache, ...), this
Expand Down
8 changes: 5 additions & 3 deletions docs/api/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,13 @@ The `ssrExchange` as [described on the "Server-side Rendering"
page.](../advanced/server-side-rendering.md).
It's of type `Options => Exchange`.

It accepts two inputs, `initialState` which is completely
It accepts three inputs, `initialState` which is completely
optional and populates the server-side rendered data with
a rehydrated cache, and `isClient` which can be set to
a rehydrated cache, `isClient` which can be set to
`true` or `false` to tell the `ssrExchange` whether to
write to (server-side) or read from (client-side) the cache.
write to (server-side) or read from (client-side) the cache, and
`staleWhileRevalidate` which will treat rehydrated data as stale
and refetch up-to-date data by reexecuring the operation using a `network-only` requests policy.

By default, `isClient` defaults to `true` when the `Client.suspense`
mode is disabled and to `false` when the `Client.suspense` mode
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/exchanges/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const cacheExchange: Exchange = ({ forward, client, dispatchDebug }) => {
};

// Reexecutes a given operation with the default requestPolicy
const reexecuteOperation = (client: Client, operation: Operation) => {
export const reexecuteOperation = (client: Client, operation: Operation) => {
return client.reexecuteOperation(
makeOperation(operation.kind, operation, {
...operation.context,
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/exchanges/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GraphQLError } from 'graphql';
import { pipe, share, filter, merge, map, tap } from 'wonka';
import { Exchange, OperationResult, Operation } from '../types';
import { CombinedError } from '../utils';
import { reexecuteOperation } from './cache';

export interface SerializedResult {
data?: string | undefined; // JSON string of data
Expand All @@ -18,6 +19,7 @@ export interface SSRData {
export interface SSRExchangeParams {
isClient?: boolean;
initialState?: SSRData;
staleWhileRevalidate?: boolean;
}

export interface SSRExchange extends Exchange {
Expand Down Expand Up @@ -91,6 +93,7 @@ const deserializeResult = (

/** The ssrExchange can be created to capture data during SSR and also to rehydrate it on the client */
export const ssrExchange = (params?: SSRExchangeParams): SSRExchange => {
const staleWhileRevalidate = !!(params && params.staleWhileRevalidate);
const data: Record<string, SerializedResult | null> = {};

// On the client-side, we delete results from the cache as they're resolved
Expand Down Expand Up @@ -137,7 +140,13 @@ export const ssrExchange = (params?: SSRExchangeParams): SSRExchange => {
filter(op => isCached(op)),
map(op => {
const serialized = data[op.key]!;
return deserializeResult(op, serialized);
const result = deserializeResult(op, serialized);
if (staleWhileRevalidate) {
result.stale = true;
reexecuteOperation(client, op);
}

return result;
})
);

Expand Down
1 change: 1 addition & 0 deletions packages/next-urql/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ export interface SSRExchange extends Exchange {
export interface WithUrqlClientOptions {
ssr?: boolean;
neverSuspend?: boolean;
staleWhileRevalidate?: boolean;
}
6 changes: 5 additions & 1 deletion packages/next-urql/src/with-urql-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export function withUrqlClient(

if (!ssr || typeof window === 'undefined') {
// We want to force the cache to hydrate, we do this by setting the isClient flag to true
ssr = ssrExchange({ initialState: urqlServerState, isClient: true });
ssr = ssrExchange({
initialState: urqlServerState,
isClient: true,
staleWhileRevalidate: options!.staleWhileRevalidate,
});
} else if (!version) {
ssr.restoreData(urqlServerState);
}
Expand Down