ReactHustle

Guide: How to Setup SSR with URQL Client in NextJS

Jasser Mark Arioste

Jasser Mark Arioste

Guide: How to Setup SSR with URQL Client in NextJS

If your website is a blog or news or something that relies on SEO to get traffic, It's essential to be able to render/generate pages on the server. In one of my previous tutorials, we went over how to set up URQL client in a NextJS App for the client side without SSR. 

This will be a continuation of the previous tutorial. We'll still be using the rick and Morty GraphQL API as our endpoint.

We'll also cover the necessary steps to implement this and why I used this approach as compared to what the docs suggested.

Why do we want Server-Side Rendering (SSR)? #

We want it for obvious reasons. Pages rendered in the server are better for SEO since they are rendered in the server. They also have no content layout shifts (CLS) and are faster because of the lesser API calls.

Project Setup #

If you want to follow along, let's create a new NextJS project from the GitHub repo of the previous tutorial.

npx create-next-app -e https://github.com/jmarioste/setup-urql-nextjs
1

Some notes about this project: 

  1. We use typescript for this for maintainability, type safety, and overall development experience.
  2. All the GraphQL types and react hooks are generated using the yarn generate command (see package.json). It uses graphql.config.yml as the spec.
  3. In next.config.js we have a proxy so that we can use "/graphql" as our endpoint. 

Let's run this project (cd setup-url-nextjs && yarn dev) and check the browser dev tools. In the network tab, you'll notice that the browser still calls our GraphQL endpoint. Our goal is to remove this call, while still being able to use useEpisodesQuery hook from the client.

Graphql Endpoint called by the browser. No SSR

Data Flow for GraphQL Apps with SSR in NextJS #

How does the data flow from server to client when using a GraphQL client? Pretty simple. From getStaticProps or getServerSideProps, we use URQL's ssrCache.extractData() method and pass all of URQL's cached data to the pageProps  as URQL_DATA. On the client side, we'll use pageProps.URQL_DATA as the initial data of our URQL Client. And when React re-hydrates our component, it uses the initial data from the URQL cache without a single API call from the browser.

Below is an image for the data flow:

Data flow for GraphQL Apps with SSR in NextJS

All that's left is to implement this and we'll have a working SSR Implementation for URQL!

Step 1: Creating an initializeClient function #

First let's create a function to initialize a urql client from both server-side and client-side. Create a file urql/initializeClient.tsx.

// urql/initializeClient.tsx
import {
  cacheExchange,
  Client,
  createClient,
  dedupExchange,
  fetchExchange,
  ssrExchange,
} from "urql";

let urqlClient: Client | null = null;

let ssrCache: ReturnType<typeof ssrExchange> | null = null;

const isServer = typeof window === "undefined";

/**
 * Function to initialize urql client. can be used both on client and server
 * @param initialState -  usually the data from the server returned as props
 * @param url - graphql endpoint
 * @returns and object with urqlClient and ssrCache
 */
export function initUrqlClient(url: string, initialState?: any) {
  if (!urqlClient) {
    //fill the client with initial state from the server.
    ssrCache = ssrExchange({ initialState, isClient: !isServer });

    urqlClient = createClient({
      url: url,
      exchanges: [
        dedupExchange,
        cacheExchange,
        ssrCache, // Add `ssr` in front of the `fetchExchange`
        fetchExchange,
      ],
    });
  } else {
    //when navigating to another page, client is already initialized.
    //lets restore that page's initial state
    ssrCache?.restoreData(initialState);
  }

  // Return both the Client instance and the ssrCache.
  return { urqlClient, ssrCache };
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445

We'll call this function later to initialize our URQL client if ever there's any server-side data.

Step 2: Create a useClient function #

Let's create a custom hook to create our URQL client. Create a file urql/useClient.tsx. It returns a URQL Client when called. We're also using useMemo and it only changes when the page props change.

// urql/useClient.tsx
import { useMemo } from "react";
import { initUrqlClient } from "./initUrqlClient";

/**
 * Simple hook to initialize the client with the pageProps.
 * @param pageProps - props of page
 * @returns urqlClient
 */
export const useClient = (pageProps: any) => {
  const urqlData = pageProps.URQL_DATA;
  const { urqlClient } = useMemo(() => {
    return initUrqlClient("/graphql", urqlData);
  }, [urqlData]);

  return urqlClient;
};
1234567891011121314151617

Step 3: Create a UrqlProvider component #

URQL already has a provider component but we'll create our own so that we can wrap it with our custom logic.

// urql/URQLProvider.tsx

import React, { ReactNode } from "react";
import { Provider } from "urql";
import { useClient } from "./useClient";

type Props = {
  children: ReactNode;
  pageProps: any;
};

const UrqlProvider = ({ children, pageProps }: Props) => {
  const client = useClient(pageProps);
  return <Provider value={client}>{children}</Provider>;
};

export default UrqlProvider;
1234567891011121314151617

Next is to use this in our _app.tsx component:


// pages/_app.tsx

import "../styles/globals.css";
import type { AppProps } from "next/app";
import UrqlProvider from "../urql/URQLProvider";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <UrqlProvider pageProps={pageProps}>
      <Component {...pageProps} />
    </UrqlProvider>
  );
}

export default MyApp;
12345678910111213141516

Step 4: Setting up getServerSideProps / getStaticProps #

Next, let's setup getServerSideProps and return the extracted cache from the URQL client. Inside pages/index.tsx let's add a getServerSideProps function.

// pages/index.tsx
...

export const getServerSideProps: GetServerSideProps = async () => {
  const { ssrCache, urqlClient } = initUrqlClient(
    "https://rickandmortyapi.com/graphql"
  );

  //call episodes query here
  const results = await urqlClient
    .query<EpisodesQuery, EpisodesQueryVariables>(EpisodesDocument, {
      page: 1,
    })
    .toPromise();

  if (results.error) {
    throw new Error(results.error.message);
  }

  return {
    props: {
      //just extract the ssrCache to pass the data to props
      URQL_DATA: ssrCache?.extractData(),
    },
  };
};
1234567891011121314151617181920212223242526

Checking the results! #

When we check again the results in the network tab, there's no more API call to our graphql API. Yay!

No more graphql api calls

If we check the page source, we can see that the graphql cached data is there.

URQL Cached Data&nbsp;

The html is also rendered.

HTML is populated with data.&nbsp;

Why not use withUrqlClient? #

The URQL Documentation recommends using withUrqlClient for setting up SSR. But I found that when I needed some custom logic for authentication, I couldn't figure things out. For me, this approach is more flexible since we gain full control over the client with the initalizeClient function and modify it as we see fit.

Conclusion #

We're able to set up URQL client with SSR in our next.js website/blog. We've made sure that SEO is great while still being able to use hooks to fetch our data on the client side.

If you like this tutorial, please leave a like or share the love. You may also subscribe to our newsletter or follow me on Twitter to get updates for future tutorials!

Resources #

Credits: Image by Michael Pointner from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle