/nextjs-apollo-client

Integrate Apollo client in a Next.js application

Primary LanguageTypeScriptMIT LicenseMIT

nextjs-apollo-client

npm Package License Coverage Status CI

Simple integration of Apollo client in a Next.js application. Easy setup with a simple but powerful API that can adapt to any use case.

Installation

npm

npm i nextjs-apollo-client @apollo/client graphql

or yarn

yarn add nextjs-apollo-client @apollo/client graphql

Setup

The example below is the bare minimum requirements to get started. For more info about the API and usage continue reading. For more advanced usage check out the example.

  1. Create a new instance of NextApolloClient
// lib/apollo/index.ts
export const { getServerSideApolloProps, useApolloClient } = new NextApolloClient({
  client: { uri: 'http://mygraph.com/graphql' }
});
  1. Add the Apollo provider to _app.tsx passing in the client
// _app.tsx
import type { AppProps } from 'next/app'
import { ApolloProvider } from '@apollo/client';
import { useApolloClient } from '../lib/apollo';

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

export default App
  1. Utilize the generated getServerSideApolloProps to start hydrating data for your pages
import type { NextPage } from 'next'
import { useQuery } from '@apollo/client';
import { getServerSideApolloProps } from '../lib/apollo';
import { USERS_QUERY } from '../gql';

const Home: NextPage = () => {
  const { data } = useQuery(USERS_QUERY);

  return (
    <div>
      ...rest
    </div>
  )
}

export const getServerSideProps = getServerSideApolloProps({
  hydrateQueries: (ctx) => [
    { query: USER_QUERY, variables: { id: ctx.query.id } }
  ],
});

export default Home;

Note: The nextjs-apollo-client package works really well with next-merge-props. Especially if you have multiple abstractions for Next.js server side data fetching functions.

import type { NextPage } from 'next'
import { useQuery } from '@apollo/client';
import { mergeProps } from 'next-merge-props';
import { getServerSideAuthProps } from '../lib/auth';
import { getServerSideApolloProps } from '../lib/apollo';
import { USERS_QUERY } from '../gql';

const Home: NextPage = () => {
  const { data } = useQuery(USERS_QUERY);

  return (
    <div>
      ...rest
    </div>
  )
}

export const getServerSideProps = mergeProps(
  getServerSideAuthProps,
  getServerSideApolloProps({
    hydrateQueries: (ctx) => [
      { query: USER_QUERY, variables: { id: ctx.query.id } }
    ],
  })
);

export default Home;

NextApolloClient

Options

Below is a table that represents the structure of that NextApolloClient options object.

Property Type Required
client ApolloClientConfig yes
hydrationMap QueryHydrationMap no (recommended)
export interface NextApolloClientOptions {
  client: ApolloClientConfig;
  hydrationMap?: QueryHydrationMap;
}

client

The only required option when creating an instance of NextApolloClient. Accepts a partial Apollo client config. For a complete list of available via partial config visit the link to the types above.

Partial Config:

export const { getServerSideApolloProps, useApolloClient } = new NextApolloClient({
  client: { uri: 'http://mygraph.com/graphql' },
});

You can also pass in a function that returns an instance of ApolloClient which allows complete control of the Apollo client setup. The function also gives you access to the current client cache state and Context object from Next.js.

Apollo client:

export const { getServerSideApolloProps, useApolloClient } = new NextApolloClient({
  client: (initialState, headers) =>
    new ApolloClient({
      ssrMode: typeof window === 'undefined',
      cache: new InMemoryCache().restore(initialState),
      link: new HttpLink({
        uri: 'http://mygraph.com/graphql',
        headers,
      }),
    }),
});

hydrationMap

You can optionally pass in a hydration map. Once you have generated a hydration you can simply reference any key from your map in the getServerSideApolloProps function to hydrate that page with any query in the hydration map.

The nextjs-apollo-client package includes a util to that you will need to use to generate the map: generateHydrationMap. To get autocompletion for your hydration map, you can provide the instance of NextApolloClient with the hydration map generic.

  1. Generate the hydration map

The generateHydrationMap function accepts a key/value pair. The key you use will also be the string you reference to hydrate queries. The value is a function that provides access to the Next.js Context object and must return a QueryOptions object.

// hydration map
const hydrationMap = generateHydrationMap({
  user: (ctx): QueryOptions<UserQueryVariables, UserQuery> => ({
    query: USER_QUERY,
    variables: { id: ctx.query.userId as string }
  }),
  users: (): QueryOptions<UsersQueryVariables, UsersQuery> => ({ query: USERS_QUERY }),
});
  1. Pass the hydration map to the NextApolloClient

Now that you've generated the map, pass it into your NextApolloClient instance and provide the hydration map generic for autocompletion.

export const { getServerSideApolloProps, useApolloClient } = new NextApolloClient<
  typeof hydrationMap
>({
  hydrationMap,
  client: { uri: 'http://mygraph.com/graphql' },
});
  1. Utilize your hydration map from getServerSideApolloProps

Now you're ready to go! Now you can reference any query in your map via a string via autocompletion.

export const getServerSideProps = getServerSideApolloProps({
  hydrateQueries: ['users'],
});

getServerSideApolloProps

The NextApolloClient instance exposes a public method called getServerSideApolloProps. This is the function that should be used to hydrate any Next.js page component with data.

Args

Below is a table that describes the accepted arguments.

Property Type Required
hydrateQueries HydrateQueries no (if not provided all queries will be made on the client)
onClientInitialized ClientInitFn no
onHydrationComplete (results: HydrationResponse) => ServerSidePropsResult no
export interface GetServerSideApolloPropsOptions<
  THydrationMap extends QueryHydrationMap,
  THydrationMapKeys = any,
  TProps = Record<string, any>
> {
  hydrateQueries?: HydrateQueries<THydrationMapKeys>;
  onClientInitialized?: (
    ctx: GetServerSidePropsContext,
    apolloClient: ApolloClient<NormalizedCacheObject>
  ) => ServerSidePropsResult<TProps>;
  onHydrationComplete?: (
    results: HydrationResponse<THydrationMap>
  ) => ServerSidePropsResult<TProps>;
}

hydrateQueries

This argument is not required, but it is required if you want to actually hydrate data on the server. There are a couple of different ways hydrateQueries can be used.

  1. Use with a hydration map

If you have generated and provided NextApolloClient with a hydration map, you can simply reference your queries via map keys.

export const getServerSideProps = getServerSideApolloProps({
  hydrateQueries: ['users'],
});
  1. Use with QueryOptions array

If your already familiar with Apollo client and have ever used refetchQueries as a side effect of mutation, this should feel familiar. Accepts a function that provides the Next.js Context object and must return an array of QueryOptions objects.

export const getServerSideProps = getServerSideApolloProps({
  hydrateQueries: [
    { query: USERS_QUERY }
  ],
});

// or as a function

export const getServerSideProps = getServerSideApolloProps({
  hydrateQueries: (ctx) => [
    { query: USER_QUERY, variables: { id: ctx.query.id } }
  ],
});

onClientInitialized

The getServerSideApolloProps function also exposes a couple of callbacks that allow you to hook into different lifecycles of the function. The onClientInitialized callback does not assist with hydration but can still hydrate data. In fact, it's basically an escape hatch that supports a wide variety of use cases.

  • Mutation operation run on the server

Not a common use case, but if you need to do it you should do it here. A return is not required, but you can provide the same return value expected in any Next.js getServerSideProps function.

export const getServerSideProps = getServerSideApolloProps<PageProps>({
  hydrateQueries: ['users'],
  onClientInitialized: async (ctx, apolloClient) => {
    const result = await apolloClient.mutate({
      mutation: MY_MUTATION,
      variables: {
        id: ctx.query.id
      }
    });

    return { props: { result } }
  },
})

onHydrationComplete

The second and last callback option is onHydrationComplete. This is used in conjunction with hydrateQueries and should be more commonly used than onClientInitialized. The callback is run after any queries from hydrateQueries are run and returns either the result of any hydration operation or errors from each query. Results for hydrated operations are mapped to there operation name. If you have provided the hydration map generic to your instance of NextApolloClient, you will get proper auto-completion for any of your hydrated operations.

A return is again not required, but you can provide the same return value expected in any Next.js getServerSideProps function.

interface PageProps {
  userId?: string; 
}

export const getServerSideProps = getServerSideApolloProps<PageProps>({
  hydrateQueries: ['user'],
  onHydrationComplete: ({ user, errors }) => {
    const currentUser = user?.data.user;
    
    if (errors.length) {
      return { notFound: true };
    }

    if (!currentUser) {
      return {
        redirect: {
          destination: '/',
          permanent: false,
        }
      }
    }

    return {
      props: { userId: currentUser.id },
    };
  }
});

useApolloClient

The only other public method returned from a NextApolloClient instance is useApolloClient. This react hook has only one use case and that's to provide the client to Apollo client's context provider. The only argument passed to the hook are the pageProps associated with the Next.js _app component.

import type { AppProps } from 'next/app'
import { ApolloProvider } from '@apollo/client';
import { useApolloClient } from '../lib/apollo';

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

export default App

Contributors

This project follows the all-contributors specification. Contributions of any kind welcome!

LICENSE

MIT