Does useQuery run on server-side rendering?
Asked Answered
M

1

14

I'm new to Next.js and have some questions about client-side rendering and server-side rendering in Next.js

  1. I see there are two ways to fetch data on Next.js. One of them is to use the useQuery hook but that is only callable on the React component function. Does it mean that it only runs when rendering the page from the client-side?
  2. I read a post about how to connect apolloClient to Next.js. It said that

always create a new instance of apolloClient for SSR and only create one instance of apolloClient for CSR

Here is the example code

  export function initializeApollo(initialState = null) {
    const _apolloClient = apolloClient ?? createApolloClient();

    // If your page has Next.js data fetching methods that use Apollo Client,
    // the initial state gets hydrated here
    if (initialState) {
      // Get existing cache, loaded during client side data fetching
      const existingCache = _apolloClient.extract();

      // Restore the cache using the data passed from
      // getStaticProps/getServerSideProps combined with the existing cached data
      _apolloClient.cache.restore({ ...existingCache, ...initialState });
    }

    // For SSG and SSR always create a new Apollo Client
    if (typeof window === "undefined") return _apolloClient;

    // Create the Apollo Client once in the client
    if (!apolloClient) apolloClient = _apolloClient;
    return _apolloClient;
  }

Can anyone explain that? I'm sorry if the questions are silly

Mongolic answered 19/4, 2021 at 14:4 Comment(0)
W
39

In Next JS:

  • SSR - Server side rendering - getServerSideProps
  • SSG - Static site generated - getStaticPaths & getStaticProps
  • CSR - Client side rendering - everything else

It is important to note that SSG functions are run server-side.

On the client, you only want to create a single global instance of Apollo Client. Creating multiple instances of Apollo Client will make it challenging to keep things in sync with the client. This difficulty is because the Apollo Cache, Apollo Link, etc., will all be stored in different instances of Apollo Client.

In Next, it is common to place the global instance of Apollo Client on the page _app.js and use the Apollo Provider. On the other client-side pages, you'd use the useQuery hook that calls your single global instance.

The server-side (SSR) functions getStaticProps or getServerSideProps do not have access to the client instance of Apollo, client instance of Next, or other server-side functions. Because of this, you must define your Apollo connection on every page that uses getStaticPaths, getStaticProps, or getServerSideProps and needs access to the Apollo client or it will not be available to the server-side calls.

Since the first rule of hooks is that they must only be called at the top level (client-side), you cannot use them in server-side functions. No, you cannot run useQuery in the Next SSR or SSG functions.

The example you provide is keeping the cache in sync and is outdated in how it is defining the client. Here is a simplified example more along the lines of the official docs.


graphqlClient.js

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';

// Used server and client side - can't use react hooks
export const graphqlClient = new ApolloClient({
  cache: new InMemoryCache(),
  link: new HttpLink({
    uri: 'YOUR_GQL_ENDPOINT',
  }),
  ssrMode: typeof window === 'undefined',
});

_app.js - a single instance that all client pages use because it wraps the whole app

import graphqlClient from 'my/path/graphqlClient';

const App = ({ Component, pageProps }) => {
  const client = graphqlClient();
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
};

Every page/component that is client-side that can use the useQuery hook because Apollo Client is wrapping the app in _app.js

Client-side query

import { gql, useQuery } from '@apollo/client';

const About = () => {
 const { data } = useQuery(YOUR_QUERY); // uses your single instance defined in _app.js
 return (
   ...
 )
}

Every page that uses SSR or SSG functions and needs access to Apollo must instantiate a new instance of Apollo.

SSG

import graphqlClient from 'my/path/graphqlClient';

//does not have access to _app.js or client and must define new Apollo Client instance
export const getStaticProps = async () => {
  const client = graphqlClient();//
  const { data } = await client.query({query: YOUR_QUERY});
};

export const getStaticPaths = async () => {
  const client = graphqlClient();
  const { data } = await client.query({query: YOUR_QUERY});
};

SSR

import graphqlClient from 'my/path/graphqlClient';

//does not have access to _app.js or client and must define new Apollo Client instance
export const getServerSideProps = async () => {
  const client = graphqlClient();
  const { data } = await client.query({query: YOUR_QUERY});
};

Lastly, to simplify things you can use graphql-code-generator to auto-generate Apollo query, mutation, etc. hooks (and types for TS users) as well as server-side compatible query and mutation functions for Next.js.

Whipple answered 20/4, 2021 at 4:3 Comment(5)
thanks for your answer. It's really detailed and helps a lot. One more question, whenever we create an instance of Apollo Client, it'll create Apollo Cache, Apollo Link, ...etc too, Right? and the Server-side doesn't have to do anything with it (Apollo cache, Apollo Link)?Mongolic
Apollo will add whatever you define when creating the client. In the above example cache and link are defined and available for both server and client. However, the server-side calls only have access to their one client and its current state. So, the SSR cache would essentially empty or only contain Apollo calls that were made in the same SSR function at the time of the calls. There are ways to configure server-side calls to be more in sync with each other but that's a whole post in itself and not always needed - especially in a SSG site.Whipple
Regarding question 1., it seems that useQuery will indeed fire HTTP queries during the server render, according to this blog post: apollographql.com/blog/apollo-client/next-js/…. It's still unclear to me what happens, because those queries are "lost" if you don't setup something like getDataFromTree or any system that take Apollo client cache and pass it back to the client.Imre
By default - Apollo useQuery is a React hooks currently (React 17) fire when the component mounts in the browser. Apollo client query is what is used on the server. By default - every Next server side function works independently from the app to feed it data - they do not know what the other server side functions do or the current application state. Here is a custom implementation of nextjs and apollo that allows you to use apollo useQuery ssr w/next - github.com/shshaw/next-apollo-ssrWhipple
@EricBurel I don't believe the blog is correct. I run several test locally, and I was never able to see the query reaching my local server. It does not even reach the apolloLink (check by adding setContext) My guess is that the QueryManager only execute the query after the render. And in server side, it will only request these ObservableQueries once getDataFromTree is executed. I was not able to confirm it 100% though. The onlyClient is still quite useful to remove part of the code that expect the result. So it avoid showing loading screen for SEO.Calamine

© 2022 - 2024 — McMap. All rights reserved.