Skip to content

Instantly share code, notes, and snippets.

@chunlea
Created October 12, 2022 05:42
Show Gist options
  • Save chunlea/2bf9d0dea6a2c38acb2deb963e20dfd5 to your computer and use it in GitHub Desktop.
Save chunlea/2bf9d0dea6a2c38acb2deb963e20dfd5 to your computer and use it in GitHub Desktop.
NextJS SSR with Apollo GraphQL Client, graphql-ruby and ActionCable
import {
ApolloClient,
from,
HttpLink,
InMemoryCache,
NormalizedCacheObject,
split,
} from '@apollo/client'
import { onError } from '@apollo/client/link/error'
import { concatPagination, getMainDefinition } from '@apollo/client/utilities'
import merge from 'deepmerge'
import ActionCableLink from 'graphql-ruby-client/subscriptions/ActionCableLink'
import isEqual from 'lodash/isEqual'
import { useMemo } from 'react'
const ssrMode = typeof window === 'undefined'
if (!ssrMode) {
window.ActionCable = require('@rails/actioncable')
}
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
let apolloClient: ApolloClient<NormalizedCacheObject>
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
const httpLink = new HttpLink({
uri: `${process.env.NEXT_PUBLIC_API_URL}/graphql`, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
})
const apolloMemoryCache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
allPosts: concatPagination(),
},
},
},
})
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
ssrMode
? () => null // We throw away subscriptions on SSR
: new ActionCableLink({
cable: window.ActionCable.createConsumer(
`${process.env.NEXT_PUBLIC_WS_URL}`
),
}),
httpLink
)
function createApolloClient() {
return new ApolloClient({
ssrMode: ssrMode,
link: from([errorLink, splitLink]),
cache: apolloMemoryCache,
})
}
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()
// Merge the initialState from getStaticProps/getServerSideProps in the existing cache
const data = merge(existingCache, initialState, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
})
// Restore the cache with the merged data
_apolloClient.cache.restore(data)
}
// For SSG and SSR always create a new Apollo Client
if (ssrMode) return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function addApolloState(
client: { cache: { extract: () => any } },
pageProps: { props: { [x: string]: any } }
) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
export function useApollo(pageProps: { [x: string]: any }) {
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment