DEV Community

Pascal Martineau
Pascal Martineau

Posted on • Updated on

SSR friendly GraphQL client

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:

import urql from "@urql/vue";
import { defineNuxtPlugin, useRuntimeConfig } from "#app";

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

  nuxtApp.vueApp.use(urql, {
    url: graphqlApiURL,
  });
});
Enter fullscreen mode Exit fullscreen mode

We provide graphqlApiURL as a public runtime configuration inside nuxt.config.ts:

publicRuntimeConfig: {
  graphqlApiURL: process.env.GRAPHQL_API_URL || "http://localhost:3000/api/graphql",
}
Enter fullscreen mode Exit fullscreen mode

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 by adding the configured SSR exchange at the proper index in the array.

The relevant code for setting this up looks like this:

// ...
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];
// ...
Enter fullscreen mode Exit fullscreen mode

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. Let's add it to our dependencies:

yarn add -D @urql/devtools
Enter fullscreen mode Exit fullscreen mode

We can then prepend it to our custom exchanges in plugins/urql.ts:

import { devtoolsExchange } from "@urql/devtools";

// ...
  // Devtools exchange
  if (nuxtApp._legacyContext?.isDev) {
    exchanges.unshift(devtoolsExchange);
  }
// ...
Enter fullscreen mode Exit fullscreen mode

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:

<script setup lang="ts">
const { data } = await useHelloQuery();
</script>

<template>
  <div id="front-page" class="prose">
    <h1>{{ data?.hello || "Nuxt3 ❤️ GraphQL" }}</h1>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

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 and subscriptions.

Latest comments (2)

Collapse
 
lu40 profile image
Lu

Thank you! I've been going crazy with buggy GraphQL clients for Nuxt 3 and this solution with @urql/vue just works flawlessly.

Collapse
 
lewebsimple profile image
Pascal Martineau

Thanks for the kind words! You can check out my latest Nuxt / GraphQL / Prisma fullstack project at github.com/lewebsimple/nuxt-graphq...