Skip to content

Using GraphQL API in getStaticProps or getServerSideProps #10946

Unanswered
matepapp asked this question in Help
Using GraphQL API in getStaticProps or getServerSideProps #10946
Mar 10, 2020 · 6 answers · 17 replies

Hey! 👋

Thanks for releasing NextJS 9.3! Although it's only a minor version bump, it's definitely a major step forward!

Unfortunately, I couldn't find any example/docs which demonstrates how to call a GraphQL API either in getStaticProps or getServerSideProps. This could be using one of the popular clients such as Apollo Client, urql, React Query, etc.. Currently we're using the next-with-apollo package which (as far as I know) basically wraps getInitialProps and enables to use useQuery or useMutation hooks in your components. Is there an equivalent solution or workaround which supports the newly released APIs?

Replies

6 suggested answers
·
17 replies

Here's a minimal example for fetching a GraphQL API: https://github.com/zeit/next.js/blob/canary/examples/cms-datocms/lib/api.js#L23-L42

The way that Apollo does tree-rendering to allow useQuery causes quite a lot of performance overhead (and general bundle size when using Apollo is quite heavy currently). So would recommend using a lightweight solution.

Using the plain Apollo client without getDataFromTree is fine though.

3 replies
@dhazeley

@timneutkens Hey, I'm curious about why Apollo's useQuery has a lot of performance overhead. In what cases do you think the performance overhead would be worth it to use Apollo client?

Also if you could point me to any resources I can read up on that would be helpful. I'm evaluating whether I should go with a next-with-apollo approach, or the more lightweight approach you just mentioned.

@timneutkens

Code can be found here: https://github.com/apollographql/react-apollo/blob/master/packages/ssr/src/getDataFromTree.ts#L23-L54

https://github.com/apollographql/react-apollo/blob/master/packages/ssr/src/getDataFromTree.ts#L13

First of all note that the following explanation is unrelated to Next.js.

The way that getDataFromTree sort of works is:

getDataFromTree is called with a React tree, eg getDataFromTree(AppTree)

It'll run renderToStaticMarkup from ReactDOMServer. Note that this is the React SSR API and means that it does a full server-render of the whole React tree.

Note that renderToStaticMarkup is a synchronous run to completion method, meaning that it can't await promises as of right now (Suspense might solve this).

In practice though you have useQuery and <Query> components deeply nested in the React tree. React can't await those as said, so this is worked around by throwing a promise every time a query is found.

When the promise is thrown that is awaited and then the rendering starts again, from the beginning of the tree.

This means that if you have nested queries you cause a lot of full server-renders.

@janus-reith

@timneutkens So the usecase you are referring to is, if you use getDataFromTree on the server if you need to make sure all your useQuery hooks or components within your codebase would actually hold data for the static page, right?
Besides maybe bundle size, there would be no concern of using useQuery in general on the client, while finding other options for fetching the data for the initial static page, or simply omitting that data if not releveant(e.g. authentication related).

I realize you already explained above how this is related to getDataFromTree, but wanted to underline that, as someone who quickly glimpses over this discussion could get the wrong impression about useQuery.

Hi,
another approach with grapqhl-request. Maybe it's interesting for the people who use swr with graphql-request.

import { Layout } from "@components/layout";
import { API } from "@graphQLClient";
import { VACCINATION_ID, VACCINATION_PAGE } from "@impfi.query";
import { request } from "graphql-request";
import { GetStaticProps, NextPage } from "next";
import React from "react";

interface IVaccinationId {
    id: string;
}

interface IVaccinationQuery {
    vaccination: IVaccinationId[];
}
interface IVaccination {
    id: string;
    name: string;
    description: string;
}

interface IProps {
    data: {
        vaccination: IVaccination[];
    };
}

const Vaccines: NextPage<IProps> = (props) => {
    const vaccination = props.data.vaccination[0];
    return (
        <Layout>
            <div className="hero m-2">
                <h1 className="text-gray-800 text-xl">{vaccination.name}</h1>
                <p>{vaccination.description}</p>
            </div>
        </Layout>
    );
};

export const getStaticPaths = async () => {
    const res: IVaccinationQuery = await request(API, VACCINATION_ID);
    const paths = res.vaccination.map((vacc) => ({ params: { id: vacc.id } }));
    return { paths, fallback: false };
};

export const getStaticProps: GetStaticProps<IProps> = async ({ params }) => {
    const data = await request(API, VACCINATION_PAGE, {
        id: params?.id
    });
    return { props: { data } };
};

export default Vaccines;

0 replies

Anyone figure this out with Apollo? Specifically, SSR with client side querying of the Apollo store: e.g. loading posts (server side with getServerSideProps) with client side paging.

@timneutkens I checked out the example above, but to your point, it doesn't really fit the use case. The example makes fetch requests, then relies on props drilling versus reading from and updating the Apollo store.

This one's been killin' me.

3 replies
@SoorajChandran

Stumbled upon this with the same issue. Since I'm a next js beginner, I thought I was missing something. This comment is an affirmation for me.

Having said that, can I use getInitialProps to server render the content for SEO?
Also, is there a way to use hooks inside getInitialProps and fetch data from Graphql?

My fallback is that I'll have to use REST to fetch data in getServerSideProps :/ Which is not nice at all.

@johnpaul89

Anyone figure this out with Apollo? Specifically, SSR with client side querying of the Apollo store: e.g. loading posts (server side with getServerSideProps) with client side paging.

@timneutkens I checked out the example above, but to your point, it doesn't really fit the use case. The example makes fetch requests, then relies on props drilling versus reading from and updating the Apollo store.

This one's been killin' me.

Check their official example it solves all the problems https://github.com/zeit/next.js/tree/canary/examples/with-apollo

@SoorajChandran

Thanks! I made it work with this approach. Left a comment on a similar discussion

lfades/next-with-apollo#114 (comment)

import { gql, ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import fetch from 'isomorphic-unfetch';
import { GetServerSideProps } from 'next';

export const getApolloClient = () => {
  const isLocalHost = process.env.NOW_URL?.includes('localhost');
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      uri: `http${isLocalHost ? '' : 's'}://${process.env.NOW_URL}/api/graphql`,
      fetch,
    }),
  });
};

const TRANSACTIONS_BY_PRODUCT_ID_QUERY = gql`
  query TRANSACTIONS_BY_PRODUCT_ID_QUERY($product_id: String!) {
    transactionsByProductId(productId: $product_id) {
      id
      bitcoinAddress
      blockchainTxId
      invoice {
        status
      }
    }
  }
`;
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  const apolloClient = getApolloClient();
  const { id } = params;
  const { data } = await apolloClient.query({
    query: TRANSACTIONS_BY_PRODUCT_ID_QUERY,
    variables: { product_id: id },
  });
  const transactions: Transaction[] = data.transactionsByProductId;
  return { props: { transactions } };
};

works for me.

11 replies
@spwr007

This is great, I have been implementing this way, but it is working for a authorised ApolloClient with headers in serverSiderProps.
Is there any way to solve it?

@AryanJ-NYC

I wish I knew. Since posting my solution above, I've dropped Apollo Client altogether and moved to a simpler approach:

import { GetServerSideProps } from "next";
import { GraphQLClient } from "graphql-request";

const graphQlClient =  new GraphQLClient(`${process.env.BASE_URL}/api/graphql`)

const TRANSACTIONS_BY_PRODUCT_ID_QUERY = /* GraphQL */ `
  query TRANSACTIONS_BY_PRODUCT_ID_QUERY($product_id: String!) {
    transactionsByProductId(productId: $product_id) {
      id
      bitcoinAddress
      blockchainTxId
      invoice {
        status
      }
    }
  }
`;
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  // @ts-expect-error
  const { id } = params;

  const data: {
    transactionsByProductId: Transaction[];
  } = await graphQlClient.request(TRANSACTIONS_BY_PRODUCT_ID_QUERY, {
    product_id: id,
  });

  const transactions = data.transactionsByProductId;
  return {
    props: {
      transactions,
    },
  };
};

Unsure how you'd add authorization headers here without making the call from the client, though.

@Manubi

Hey,
this should work. Just add the header after the endpoint.

  const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr'

  const graphQLClient = new GraphQLClient(endpoint, {
    headers: {
      authorization: 'Bearer MY_TOKEN',
    },
  })

Here is an example #13202 if you want to query your API route GraphQL endpoint in order to do Static Generation with getStaticProps and getStaticPaths. The example may be helpful to do the server / static rendering aspect, you would still need a graphql client if you needed to query client side, but the examples folder has quite a few that illustrate options available https://github.com/vercel/next.js/tree/canary/examples

0 replies

I had a similar issue, was getting "XMLHttpRequest is not defined". My problem was that I was using unfetch instead of fetch and didn't remove it in getStaticProps

0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Help
Labels
None yet