loading...
Cover image for NextJS x GraphQL x Apollo Client SSR

NextJS x GraphQL x Apollo Client SSR

ghackdev profile image ghack.dev ・2 min read

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!

Posted on Apr 17 by:

ghackdev profile

ghack.dev

@ghackdev

Free programming, web and mobile app development courses and tutorials.

Discussion

markdown guide
 
 

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?

 

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.

 

Ah that explains it.
Thank you!

 

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

 

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

 

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

 

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

 

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 :)

 

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