SSR friendly GraphQL client
Tags: graphql, nuxt3, ssr, urql
Published: 2022-02-23 13:55:20

In the previous article, we generated type safe GraphQL composables from `.graphql` files to be used in our VueJS frontend code. Under the hood, the generated functions rely on the `@urql/vue` client, which should be configured first before attempting to access our GraphQL operations.

## Configuring @urql/vue with a Nuxt3 plugin

To get started, we can provide a basic `@urql/vue` client with a plugin in `plugins/urql.ts`:
```typescript
import urql from "@urql/vue";
import { defineNuxtPlugin, useRuntimeConfig } from "#app";

export default defineNuxtPlugin((nuxtApp) => {
  const { graphqlApiURL } = useRuntimeConfig();

  nuxtApp.vueApp.use(urql, {
    url: graphqlApiURL,
  });
});
```

We provide `graphqlApiURL` as a public runtime configuration inside `nuxt.config.ts`:
```typescript
publicRuntimeConfig: {
  graphqlApiURL: process.env.GRAPHQL_API_URL || "http://localhost:3000/api/graphql",
}
```
_We could try to determine the API URL based on Nuxt options at build or run time, but it's so much easier to define it explicitly._

### Setting up the SSR exchange

When executing queries in a page and/or component(s), the fetched results can be combined and serialized in the page payload sent by the server, then rehydrated by the client upon rendering.

To achieve this, we'll need to override `urql`'s [default exchanges](https://formidable.com/open-source/urql/docs/architecture/#the-exchanges) by adding the configured SSR exchange at the proper index in the array.

The relevant code for setting this up looks like this:
```typescript
// ...
import urql, { cacheExchange, dedupExchange, fetchExchange, ssrExchange } from "@urql/vue";

// ...
  // Create SSR exchange
  const ssr = ssrExchange({
    isClient: process.client,
  });

  // Extract SSR payload once app is rendered on the server
  if (process.server) {
    nuxtApp.hook("app:rendered", () => {
      nuxtApp.payload?.data && (nuxtApp.payload.data.urql = ssr.extractData());
    });
  }

  // Restore SSR payload once app is created on the client
  if (process.client) {
    nuxtApp.hook("app:created", () => {
      nuxtApp.payload?.data && ssr.restoreData(nuxtApp.payload.data.urql);
    });
  }

  // Custom exchanges
  const exchanges = [dedupExchange, cacheExchange, ssr, fetchExchange];
// ...
```

The `exchanges` array is then passed to `urql`'s options to override the default ones.

_I'm not 100% sure about the server/client payload handling code above but it works (there might be some Nuxt helpers for achieving this in a simpler way)._

### Bonus: Configuring the devtools exchange

It can be useful to debug `urql` with its official [devtools](https://formidable.com/open-source/urql/docs/advanced/debugging/#devtools). Let's add it to our dependencies:
```shell
yarn add -D @urql/devtools
```

We can then prepend it to our custom exchanges in `plugins/urql.ts`:
```typescript
import { devtoolsExchange } from "@urql/devtools";

// ...
  // Devtools exchange
  if (nuxtApp._legacyContext?.isDev) {
    exchanges.unshift(devtoolsExchange);
  }
// ...
```

And voilà, our app should now expose debug events in the devtools panel, provided the extension is properly installed in your browser.

## Using the generated operations

Since our generated operations can be auto-imported (see the previous article), executing our `{ hello }` query is as simple as this:
```vue
<script setup lang="ts">
const { data } = await useHelloQuery();
</script>

<template>
  <div id="front-page" class="prose">
    <h1>{{ data?.hello || "Nuxt3 ❤️ GraphQL" }}</h1>
  </div>
</template>
```
How amazing is that? Thanks to the `<script setup>` syntax and auto-importing, one line of TypeScript is all it takes to execute our generated query operation and access the result with full type safety!

### What about mutations (and subscriptions)?

Using generated mutation operations isn't very different than using queries, the main difference being that the mutation isn't executed automatically. Instead, we have to use the `executeMutation` method on the result object returned from our generated composable, i.e. `useSomeMutation`.

As for subscriptions, we'll need to configure some kind of transport to use them. This will be the subject of an upcoming article in this exciting series!

For more information, please refer to the `@urql/vue` documentation on [mutations](https://formidable.com/open-source/urql/docs/basics/vue/#mutations) and [subscriptions](https://formidable.com/open-source/urql/docs/advanced/subscriptions/#vue).