Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1---
2title: Server-side Rendering
3order: 3
4---
5
6# Server-side Rendering
7
8In server-side rendered applications we often need to set our application up so that data will be
9fetched on the server-side and later sent down to the client for hydration. `urql` supports this
10through the `ssrExchange.`
11
12## The SSR Exchange
13
14The `ssrExchange` has two functions. On the server-side it's able to gather all results as they're
15being fetched, which can then be serialized and sent to the client. On the client-side it's able to
16use these serialized results to rehydrate and render the application without refetching this data.
17
18To start out with the `ssrExchange` we have to add the exchange to our `Client`:
19
20```js
21import { Client, cacheExchange, fetchExchange, ssrExchange } from '@urql/core';
22
23const isServerSide = typeof window === 'undefined';
24
25// The `ssrExchange` must be initialized with `isClient` and `initialState`
26const ssr = ssrExchange({
27 isClient: !isServerSide,
28 initialState: !isServerSide ? window.__URQL_DATA__ : undefined,
29});
30
31const client = new Client({
32 exchanges: [
33 cacheExchange,
34 ssr, // Add `ssr` in front of the `fetchExchange`
35 fetchExchange,
36 ],
37});
38```
39
40The `ssrExchange` must be initialized with the `isClient` and `initialState` options. The `isClient`
41option 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`.
42
43Optionally, 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
44refetch `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.
45
46The `initialState` option should be set to the serialized data you retrieve on your server-side.
47This data may be retrieved using methods on `ssrExchange()`. You can retrieve the serialized data
48after server-side rendering using `ssr.extractData()`:
49
50```js
51// Extract and serialise the data like so from the `ssr` instance
52// we've previously created by calling `ssrExchange()`
53const data = JSON.stringify(ssr.extractData());
54
55const markup = ''; // The render code for our framework goes here
56
57const html = `
58<html>
59 <body>
60 <div id="root">${markup}</div>
61 <script>
62 window.__URQL_DATA__ = JSON.parse(${data});
63 </script>
64 </body>
65</html>
66`;
67```
68
69This will provide `__URQL_DATA__` globally, which we've used in our first example to inject data into
70the `ssrExchange` on the client-side.
71
72Alternatively you can also call `restoreData` as long as this call happens synchronously before the
73`client` starts receiving queries.
74
75```js
76const isServerSide = typeof window === 'undefined';
77const ssr = ssrExchange({ isClient: !isServerSide });
78
79if (!isServerSide) {
80 ssr.restoreData(window.__URQL_DATA__);
81}
82```
83
84## Using `react-ssr-prepass`
85
86In the previous examples we've set up the `ssrExchange`, however with React this still requires us
87to manually execute our queries before rendering a server-side React app [using `renderToString`
88or `renderToNodeStream`](https://reactjs.org/docs/react-dom-server.html#rendertostring).
89
90For React, `urql` has a "Suspense mode" that [allows data fetching to interrupt
91rendering](https://reactjs.org/docs/concurrent-mode-suspense.html). However, Suspense is
92not supported by React during server-side rendering.
93
94Using [the `react-ssr-prepass` package](https://github.com/FormidableLabs/react-ssr-prepass) however,
95we can implement a prerendering step before we let React server-side render, which allows us to
96automatically fetch all data that the app requires with Suspense. This technique is commonly
97referred to as a "two-pass approach", since our React element is traversed twice.
98
99To set this up, first we'll install `react-ssr-prepass`. It has a peer dependency on `react-is`
100and `react`.
101
102```sh
103yarn add react-ssr-prepass react-is react-dom
104# or
105npm install --save react-ssr-prepass react-is react-dom
106```
107
108Next, we'll modify our server-side code and add `react-ssr-prepass` in front of `renderToString`.
109
110```jsx
111import { renderToString } from 'react-dom/server';
112import prepass from 'react-ssr-prepass';
113
114import {
115 Client,
116 cacheExchange,
117 fetchExchange,
118 ssrExchange,
119 Provider,
120} from 'urql';
121
122const handleRequest = async (req, res) => {
123 // ...
124 const ssr = ssrExchange({ isClient: false });
125
126 const client = new Client({
127 url: 'https://??',
128 suspense: true, // This activates urql's Suspense mode on the server-side
129 exchanges: [cacheExchange, ssr, fetchExchange]
130 });
131
132 const element = (
133 <Provider value={client}>
134 <App />
135 </Provider>
136 );
137
138 // Using `react-ssr-prepass` this prefetches all data
139 await prepass(element);
140 // This is the usual React SSR rendering code
141 const markup = renderToString(element);
142 // Extract the data after prepass and rendering
143 const data = JSON.stringify(ssr.extractData());
144
145 res.status(200).send(`
146 <html>
147 <body>
148 <div id="root">${markup}</div>
149 <script>
150 window.__URQL_DATA__ = JSON.parse(${data});
151 </script>
152 </body>
153 </html>
154 `);
155};
156```
157
158It's important to set enable the `suspense` option on the `Client`, which switches it to support
159React suspense.
160
161### With Preact
162
163If you're using Preact instead of React, there's a drop-in replacement package for
164`react-ssr-prepass`, which is called `preact-ssr-prepass`. It only has a peer dependency on Preact,
165and we can install it like so:
166
167```sh
168yarn add preact-ssr-prepass preact
169# or
170npm install --save preact-ssr-prepass preact
171```
172
173All above examples for `react-ssr-prepass` will still be the same, except that instead of
174using the `urql` package we'll have to import from `@urql/preact`, and instead of `react-ssr-prepass`
175we'll have to import from. `preact-ssr-prepass`.
176
177## Next.js
178
179If you're using [Next.js](https://nextjs.org/) you can save yourself a lot of work by using
180`@urql/next`. The `@urql/next` package is set to work with Next 13.
181
182To set up `@urql/next`, first we'll install `@urql/next` and `urql` as
183peer dependencies:
184
185```sh
186yarn add @urql/next urql graphql
187# or
188npm install --save @urql/next urql graphql
189```
190
191We now have two ways to leverage `@urql/next`, one being part of a Server component
192or being part of the general `app/` folder.
193
194In a server component we will import from `@urql/next/rsc`
195
196```ts
197// app/page.tsx
198import React from 'react';
199import { cacheExchange, createClient, fetchExchange, gql } from '@urql/core';
200import { registerUrql } from '@urql/next/rsc';
201
202const makeClient = () => {
203 return createClient({
204 url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
205 exchanges: [cacheExchange, fetchExchange],
206 });
207};
208
209const { getClient } = registerUrql(makeClient);
210
211export default async function Home() {
212 const result = await getClient().query(PokemonsQuery, {});
213 return (
214 <main>
215 <h1>This is rendered as part of an RSC</h1>
216 <ul>
217 {result.data.pokemons.map((x: any) => (
218 <li key={x.id}>{x.name}</li>
219 ))}
220 </ul>
221 </main>
222 );
223}
224```
225
226When we aren't leveraging server components we will import the things we will
227need to do a bit more setup, we go to the `client` component's layout file and
228structure it as the following.
229
230```tsx
231// app/client/layout.tsx
232'use client';
233
234import { useMemo } from 'react';
235import { UrqlProvider, ssrExchange, cacheExchange, fetchExchange, createClient } from '@urql/next';
236
237export default function Layout({ children }: React.PropsWithChildren) {
238 const [client, ssr] = useMemo(() => {
239 const ssr = ssrExchange({
240 isClient: typeof window !== 'undefined',
241 });
242 const client = createClient({
243 url: 'https://trygql.formidable.dev/graphql/web-collections',
244 exchanges: [cacheExchange, ssr, fetchExchange],
245 suspense: true,
246 });
247
248 return [client, ssr];
249 }, []);
250
251 return (
252 <UrqlProvider client={client} ssr={ssr}>
253 {children}
254 </UrqlProvider>
255 );
256}
257```
258
259It is important that we pass both a client as well as the `ssrExchange` to the `Provider`
260this way we will be able to restore the data that Next streams to the client later on
261when we are hydrating.
262
263The next step is to query data in your client components by means of the `useQuery`
264method defined in `@urql/next`.
265
266```tsx
267// app/client/page.tsx
268'use client';
269
270import Link from 'next/link';
271import { Suspense } from 'react';
272import { useQuery, gql } from '@urql/next';
273
274export default function Page() {
275 return (
276 <Suspense>
277 <Pokemons />
278 </Suspense>
279 );
280}
281
282const PokemonsQuery = gql`
283 query {
284 pokemons(limit: 10) {
285 id
286 name
287 }
288 }
289`;
290
291function Pokemons() {
292 const [result] = useQuery({ query: PokemonsQuery });
293 return (
294 <main>
295 <h1>This is rendered as part of SSR</h1>
296 <ul>
297 {result.data.pokemons.map((x: any) => (
298 <li key={x.id}>{x.name}</li>
299 ))}
300 </ul>
301 </main>
302 );
303}
304```
305
306The data queried in the above component will be rendered on the server
307and re-hydrated back on the client. When using multiple Suspense boundaries
308these will also get flushed as they complete and re-hydrated.
309
310> When data is used throughout the application we advise against
311> rendering this as part of a server-component so you can benefit
312> from the client-side cache.
313
314### Invalidating data from a server-component
315
316When data is rendered by a server component but you dispatch a mutation
317from a client component the server won't automatically know that the
318server-component on the client needs refreshing. You can forcefully
319tell the server to do so by using the Next router and calling `.refresh()`.
320
321```tsx
322import { useRouter } from 'next/navigation';
323
324const Todo = () => {
325 const router = useRouter();
326 const executeMutation = async () => {
327 await updateTodo();
328 router.refresh();
329 };
330};
331```
332
333### Disabling RSC fetch caching
334
335You can pass `fetchOptions: { cache: "no-store" }` to the `createClient`
336constructor to avoid running into cached fetches with server-components.
337
338## Legacy Next.js (pages)
339
340If you're using [Next.js](https://nextjs.org/) with the classic `pages` you can instead use `next-urql`.
341To set up `next-urql`, first we'll install `next-urql` with `react-is` and `urql` as peer dependencies:
342
343```sh
344yarn add next-urql react-is urql graphql
345# or
346npm install --save next-urql react-is urql graphql
347```
348
349The peer dependency on `react-is` is inherited from `react-ssr-prepass` requiring it.
350
351Note that if you are using Next before v9.4 you'll need to polyfill fetch, this can be
352done through [`isomorphic-unfetch`](https://www.npmjs.com/package/isomorphic-unfetch).
353
354We're now able to wrap any page or `_app.js` using the `withUrqlClient` higher-order component. If
355we wrap `_app.js` we won't have to wrap any individual page.
356
357```js
358// pages/index.js
359import React from 'react';
360import { useQuery } from 'urql';
361import { withUrqlClient } from 'next-urql';
362
363const Index = () => {
364 const [result] = useQuery({
365 query: '{ test }',
366 });
367
368 // ...
369};
370
371export default withUrqlClient((_ssrExchange, ctx) => ({
372 // ...add your Client options here
373 url: 'http://localhost:3000/graphql',
374}))(Index);
375```
376
377The `withUrqlClient` higher-order component function accepts the usual `Client` options as
378an argument. This may either just be an object, or a function that receives the Next.js'
379`getInitialProps` context.
380
381One added caveat is that these options may not include the `exchanges` option because `next-urql`
382injects the `ssrExchange` automatically at the right location. If you're setting up custom exchanges
383you'll need to instead provide them in the `exchanges` property of the returned client object.
384
385```js
386import { cacheExchange, fetchExchange } from '@urql/core';
387
388import { withUrqlClient } from 'next-urql';
389
390export default withUrqlClient(ssrExchange => ({
391 url: 'http://localhost:3000/graphql',
392 exchanges: [cacheExchange, ssrExchange, fetchExchange],
393}))(Index);
394```
395
396Unless the component that is being wrapped already has a `getInitialProps` method, `next-urql` won't add its own SSR logic, which automatically fetches queries during
397server-side rendering. This can be explicitly enabled by passing the `{ ssr: true }` option as a second argument to `withUrqlClient`.
398
399When you are using `getStaticProps`, `getServerSideProps`, or `getStaticPaths`, you should opt-out of `Suspense` by setting the `neverSuspend` option to `true` in your `withUrqlClient` configuration.
400During the prepass of your component tree `next-urql` can't know how these functions will alter the props passed to your page component. This injection
401could change the `variables` used in your `useQuery`. This will lead to error being thrown during the subsequent `toString` pass, which isn't supported in React 16.
402
403### SSR with { ssr: true }
404
405The `withUrqlClient` only wraps our component tree with the context provider by default.
406To enable SSR, the easiest way is specifying the `{ ssr: true }` option as a second
407argument to `withUrqlClient`:
408
409```js
410import { cacheExchange, fetchExchange } from '@urql/core';
411
412import { withUrqlClient } from 'next-urql';
413
414export default withUrqlClient(
415 ssrExchange => ({
416 url: 'http://localhost:3000/graphql',
417 exchanges: [cacheExchange, ssrExchange, fetchExchange],
418 }),
419 { ssr: true } // Enables server-side rendering using `getInitialProps`
420)(Index);
421```
422
423Be aware that wrapping the `_app` component using `withUrqlClient` with the `{ ssr: true }`
424option disables Next's ["Automatic Static
425Optimization"](https://nextjs.org/docs/advanced-features/automatic-static-optimization) for
426**all our pages**. It is thus preferred to enable server-side rendering on a per-page basis.
427
428### SSR with getStaticProps or getServerSideProps
429
430Enabling server-side rendering using `getStaticProps` and `getServerSideProps` is a little
431more involved, but has two major benefits:
432
4331. allows **direct schema execution** for performance optimisation
4342. allows performing extra operations in those functions
435
436To make the functions work with the `withUrqlClient` wrapper, return the `urqlState` prop
437with the extracted data from the `ssrExchange`:
438
439```js
440import { withUrqlClient, initUrqlClient } from 'next-urql';
441import { ssrExchange, cacheExchange, fetchExchange, useQuery } from 'urql';
442
443const TODOS_QUERY = `
444 query { todos { id text } }
445`;
446
447function Todos() {
448 const [res] = useQuery({ query: TODOS_QUERY });
449 return (
450 <div>
451 {res.data.todos.map(todo => (
452 <div key={todo.id}>
453 {todo.id} - {todo.text}
454 </div>
455 ))}
456 </div>
457 );
458}
459
460export async function getStaticProps(ctx) {
461 const ssrCache = ssrExchange({ isClient: false });
462 const client = initUrqlClient(
463 {
464 url: 'your-url',
465 exchanges: [cacheExchange, ssrCache, fetchExchange],
466 },
467 false
468 );
469
470 // This query is used to populate the cache for the query
471 // used on this page.
472 await client.query(TODOS_QUERY).toPromise();
473
474 return {
475 props: {
476 // urqlState is a keyword here so withUrqlClient can pick it up.
477 urqlState: ssrCache.extractData(),
478 },
479 revalidate: 600,
480 };
481}
482
483export default withUrqlClient(
484 ssr => ({
485 url: 'your-url',
486 })
487 // Cannot specify { ssr: true } here so we don't wrap our component in getInitialProps
488)(Todos);
489```
490
491The above example will make sure the page is rendered as a static-page, It's important that
492you fully pre-populate your cache so in our case we were only interested in getting our todos,
493if there are child components relying on data you'll have to make sure these are fetched as well.
494
495The `getServerSideProps` and `getStaticProps` functions only run on the **server-side** — any
496code used in them is automatically stripped away from the client-side bundle using the
497[next-code-elimination tool](https://next-code-elimination.vercel.app/). This allows **executing
498our schema directly** using `@urql/exchange-execute` if we have access to our GraphQL server:
499
500```js
501import { withUrqlClient, initUrqlClient } from 'next-urql';
502import { ssrExchange, cacheExchange, fetchExchange, useQuery } from 'urql';
503import { executeExchange } from '@urql/exchange-execute';
504
505import { schema } from '@/server/graphql'; // our GraphQL server's executable schema
506
507const TODOS_QUERY = `
508 query { todos { id text } }
509`;
510
511function Todos() {
512 const [res] = useQuery({ query: TODOS_QUERY });
513 return (
514 <div>
515 {res.data.todos.map(todo => (
516 <div key={todo.id}>
517 {todo.id} - {todo.text}
518 </div>
519 ))}
520 </div>
521 );
522}
523
524export async function getServerSideProps(ctx) {
525 const ssrCache = ssrExchange({ isClient: false });
526 const client = initUrqlClient(
527 {
528 url: '', // not needed without `fetchExchange`
529 exchanges: [
530 cacheExchange,
531 ssrCache,
532 executeExchange({ schema }), // replaces `fetchExchange`
533 ],
534 },
535 false
536 );
537
538 await client.query(TODOS_QUERY).toPromise();
539
540 return {
541 props: {
542 urqlState: ssrCache.extractData(),
543 },
544 };
545}
546
547export default withUrqlClient(ssr => ({
548 url: 'your-url',
549}))(Todos);
550```
551
552Direct schema execution skips one network round trip by accessing your resolvers directly
553instead of performing a `fetch` API call.
554
555### Stale While Revalidate
556
557If 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.
558We 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.
559
560```js
561export default withUrqlClient(
562 ssr => ({
563 url: 'your-url',
564 }),
565 { staleWhileRevalidate: true }
566)(...);
567```
568
569Now, 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.
570During 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.
571
572### Resetting the client instance
573
574In rare scenario's you possibly will have to reset the client instance (reset all cache, ...), this
575is an uncommon scenario, and we consider it "unsafe" so evaluate this carefully for yourself.
576
577When this does seem like the appropriate solution any component wrapped with `withUrqlClient` will receive the `resetUrqlClient`
578property, when invoked this will create a new top-level client and reset all prior operations.
579
580## Vue Suspense
581
582In Vue 3 a [new feature was introduced](https://vuedose.tips/go-async-in-vue-3-with-suspense/) that
583natively allows components to suspend while data is loading, which works universally on the server
584and on the client, where a replacement loading template is rendered on a parent while data is
585loading.
586
587We've previously seen how we can change our usage of `useQuery`'s `PromiseLike` result to [make use
588of Vue Suspense on the "Queries" page.](../basics/vue.md#vue-suspense)
589
590Any component's `setup()` function can be updated to instead be an `async setup()` function, in
591other words, to return a `Promise` instead of directly returning its data. This means that we can
592update any `setup()` function to make use of Suspense.
593
594On the server-side we can then use `@vue/server-renderer`'s `renderToString`, which will return a
595`Promise` that resolves when all suspense-related loading is completed.
596
597```jsx
598import { createSSRApp } = from 'vue'
599import { renderToString } from '@vue/server-renderer';
600
601import urql, {
602 createClient,
603 cacheExchange,
604 fetchExchange,
605 ssrExchange
606} from '@urql/vue';
607
608const handleRequest = async (req, res) => {
609 // This is where we'll put our root component
610 const app = createSSRApp(Root)
611
612 // NOTE: All we care about here is that the SSR Exchange is included
613 const ssr = ssrExchange({ isClient: false });
614 app.use(urql, {
615 exchanges: [cacheExchange, ssr, fetchExchange]
616 });
617
618 const markup = await renderToString(app);
619
620 const data = JSON.stringify(ssr.extractData());
621
622 res.status(200).send(`
623 <html>
624 <body>
625 <div id="root">${markup}</div>
626 <script>
627 window.__URQL_DATA__ = JSON.parse(${data});
628 </script>
629 </body>
630 </html>
631 `);
632};
633```
634
635This effectively renders our Vue app on the server-side and provides the client-side data for
636rehydration that we've set up in the above [SSR Exchange section](#the-ssr-exchange) to use.