DEV Community

Cover image for NextJS x GraphQL x Apollo Client SSR
ghack.dev
ghack.dev

Posted on

NextJS x GraphQL x Apollo Client SSR

I've built my personal blog (https://www.ghack.dev/) using NextJS, GraphQL with Apollo Server and Apollo Client. My blog is a Progressive and Isomorphic Web App. I experienced a tricky challenge when I want to do Server-side rendering using Apollo Client.

Why i need to do SSR?

I need to do SSR because I want to do SEO and I also need the data from the GraphQL query to fill page title and some meta tags in some pages in my personal blog.

I have read the documentation about Server-side rendering using Apollo Client here :
https://www.apollographql.com/docs/react/performance/server-side-rendering/

But I still confuse about how to implement it on my NextJS project. And then I got inspired by this library :
https://github.com/lfades/next-with-apollo

That is a very simple library that we can use to enable SSR to our NextJS x Apollo Client project. We only need to wrap our Page Component with withApollo HOC from the library.

The idea is to execute getDataFromTree method from the @apollo/react-ssr library and put an extracted AppTree component from the context as a parameter. Just like what's explained in the documentation.

Because I want a very simple implementation and I won't use HOC to execute the getDataFromTree method. I want to execute getDataFromTree inside the getInitialProps in my _app Component.

Maybe you won't include Apollo in all of your pages, in some cases, using HOC is a better approach.

Finally, I've succeed to do that and this is my code :

import React, { useReducer, useEffect, Reducer } from 'react';
import Head from 'next/head';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { getDataFromTree } from '@apollo/react-ssr';
import fetch from 'isomorphic-fetch';
import AppContext, { Action, State, reducer, initialValues, appActions } from '@contexts/AppContext';
import useScroll from '@hooks/useScroll';

const link = createHttpLink({
  uri: 'https://my-graphql-server.com/graphql',
  fetch: fetch,
})

const apollo = new ApolloClient({  
  link,
  cache: new InMemoryCache()
});


const App = ({ Component, pageProps }) => {
  const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, initialValues);
  const [scroll] = useScroll();

  useEffect(() => {
    dispatch(appActions.setScrollPosition(scroll));
  }, [scroll]);

  return (
    <AppContext.Provider
      value={{
        state,
        dispatch,
      }}
    >
      <ApolloProvider
        client={apollo}
      >
        <Component {...pageProps} />
      </ApolloProvider>
    </AppContext.Provider>
  )
};

App.getInitialProps = async ({ Component, ctx }: any) => {  
  let pageProps = {} as any;
  const apolloState = {data: {}};
  const { AppTree } = ctx;

  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx);
  }

  if(typeof window === 'undefined') {
    if (ctx.res && (ctx.res.headersSent || ctx.res.finished)) {
      return pageProps;
    }

    try {
      const props = { ...pageProps, apolloState, apollo };
      const appTreeProps =
        'Component' in ctx ? props : { pageProps: props };
      await getDataFromTree(<AppTree {...appTreeProps} />);
    }catch(error) {
      console.error(
        'GraphQL error occurred [getDataFromTree]',
        error
      );
    }

    Head.rewind();

    apolloState.data = apollo.cache.extract();
  }

  return { pageProps };
}  

export default App;

I hope this information can be useful for you. And please, if you have a better idea to improve my code. Please leave a comment below!

Discussion (12)

Collapse
alexxxpopa profile image
Alex Popa

Thank you for your article

What version of @apollo/react-ssr are you using?

Have you tried this also with the latest version 4.0.0 on both @apollo/react-ssr and @apollo/react-hooks?
I have tried updating my project and encountered a ton of issues.
From my understanding getDataFromTree has been removed. I have followed this thread
github.com/vercel/next.js/discussi...

Collapse
shuzootani profile image
shuzo

Thanks for sharing and ghack.dev loads very fast!!
Is there anything that you did to improve the loading speed?

Collapse
ghackdev profile image
ghack.dev Author • Edited

Hey thanks! ghack.dev is a progressive web app and it's an isomorphic web app. Yeah it's fast by default, but believe me it can be faster if I did more optimizations because the lighthouse scores are not pretty good enough. :)

The image file sizes, the css size, there are still a lot of homework to do :D

Collapse
mattheslington profile image
Matt Heslington

Great looking site!

Collapse
ghackdev profile image
ghack.dev Author

Thanks brother!

Collapse
rezaavoor profile image
Reza Hosseini

Just wondering what the benefit of using graphql api is when using Next.js. Can't you just talk to the database directly in Nextjs backend part instead of having an extra api doing that for you?

Collapse
ghackdev profile image
ghack.dev Author • Edited

Actually, GraphQL is my only backend. I'm consuming some third-party APIs like dev.to API and Youtube API that I orchestrated in my GraphQL backend. I also retrieve some data from MongoDB.

I preferred to use GraphQL rather than Rest API because with GraphQL I can do a lot of things and have good documentation about the data that will be consumed by the front-end apps, web and I also have a plan to build a mobile app.

The benefits of using GraphQL are also I can use Apollo Client that has a lot of awesome features like caching, optimistic response and it also can be rendered on the server for SEO purposes so I think it fits my needs, because I want to build an Isomorphic web app.

Collapse
rezaavoor profile image
Reza Hosseini

Ah that explains it.
Thank you!

Collapse
pkellner profile image
Peter Kellner

I'm struggling to find a good way to integrate GraphQL/NextJS and jwttoken auth. any hints?

Collapse
ghackdev profile image
ghack.dev Author

you could do it easily by putting the jwt token on cookie and put it on Apollo Client's link option.

Collapse
krpecd profile image
David Krpec

Thanks for great Article!

Maybe you can remove the AppContext from the code. To make the code cleaner, more corresponding with the title and easier to understand :)

Collapse
ghackdev profile image
ghack.dev Author

Yeah. great Idea David! That's unnecessary. out of context :)