DEV Community 👩‍💻👨‍💻

DEV Community 👩‍💻👨‍💻 is a community of 967,611 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
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.

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.