Next JS 13 (AppDir and RSC) with ApolloClient
See original GitHub issueIntended outcome:
After moving my app from the pages layout to the new app directory, I should be able to make simple queries to my GraphQL endpoint using apollo-client
and graphql-codegen
Actual outcome: With the new NextJS 13 server rendered components, its no longer possible to provide the apollo context at the root of the app and then useQuery() to fetch the data.
How to reproduce the issue: I initially tried to match up the pageProps that I was using in the previous version:
config/apolloClient.ts
export const APOLLO_STATE_PROP_NAME = "__APOLLO_STATE__";
let apolloClient: ApolloClient<NormalizedCacheObject> | null = null;
type SchemaContext =
| SchemaLink.ResolverContext
| SchemaLink.ResolverContextFunction;
function createIsomorphicLink(_?: SchemaContext) {
const httpLink = new HttpLink({
uri: `${
process.env.NODE_ENV == "production"
? process.env.GRAPHQL_URL_PROD
: "http://localhost:5001/life-hive/us-central1/graphql"
}`,
credentials: "same-origin",
});
return from([httpLink]);
}
function createApolloClient(ctx?: SchemaContext) {
return new ApolloClient({
name: "life-hive-dashboard",
ssrMode: typeof window === "undefined",
link: createIsomorphicLink(ctx || undefined),
cache: new InMemoryCache({
typePolicies: {
Customer: { keyFields: ["customer_id"] },
ApiaryDevice: { keyFields: ["device_id"] },
HiveDevice: { keyFields: ["device_id"] },
CombDevice: { keyFields: ["comb_id"] },
Treatment: { keyFields: ["treatment_id", "device_id"] },
DeviceEvent: { keyFields: ["event_id"] },
CombState: { keyFields: ["id"] },
DeviceState: { keyFields: ["id"] },
Failure: { keyFields: ["failure_id"] },
Query: {
fields: {
getGlobalIDs: relayStylePagination(),
},
},
},
}),
});
}
interface InitApollo {
initialState?: any;
ctx?: SchemaContext;
}
export function initializeApollo({ ctx, initialState }: InitApollo) {
const _apolloClient = apolloClient ?? createApolloClient(ctx || undefined);
// 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 (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function addApolloState(
client: ApolloClient<NormalizedCacheObject>,
pageProps: { props: any },
) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
return pageProps;
}
export function useApollo(pageProps: any) {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(
() => initializeApollo({ initialState: state }),
[state],
);
return store;
}
pages/_app.tsx
function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
const apolloClient = useApollo(pageProps);
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
With the new Next13 AppDir they suggest creating a provider.tsx which is rendered client side and used to wrap the children in the layout.tsx as stated in their docs, but since i’m adding the apollo state to the app in my useApollo() this doesn’t work.
So i’ve tried to make a simplier version by following some other blog posts on using Next and Apollo to initialize the client and then try useQuery() in a RSC:
config/apollo_config.ts
function createApolloClient() {
return new ApolloClient({
name: 'internal-dashboard',
uri: process.env.GRAPHQL_URL_PROD,
cache: new InMemoryCache(),
});
}
export function useApollo() {
const client = useMemo(() => createApolloClient(), []);
return client;
}
app/providers.tsx
'use client';
import { ApolloProvider } from '@apollo/client';
import { useApollo } from '../config/apollo_client';
export default function Provider({ children }: { children: React.ReactNode }) {
const client = useApollo();
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
When running on a server rendered component i get the following error:
TypeError: Cannot read properties of undefined (reading 'Symbol(__APOLLO_CONTEXT__)')
at Object.getApolloContext (webpack-internal:///(sc_server)/./node_modules/@apollo/client/react/context/context.cjs:22:49)
at useApolloClient (webpack-internal:///(sc_server)/./node_modules/@apollo/client/react/hooks/hooks.cjs:27:46)
at Object.useQuery (webpack-internal:///(sc_server)/./node_modules/@apollo/client/react/hooks/hooks.cjs:100:29)
at useProductsQuery (webpack-internal:///(sc_server)/./graphql/generated/graphql-codegen.tsx:239:56)
at ProductContent (webpack-internal:///(sc_server)/./app/(product)/ProductContent.tsx:12:125)
at attemptResolveElement (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1207:42)
at resolveModelToJSON (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1660:53)
at Object.toJSON (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1121:40)
at stringify (<anonymous>)
at processModelChunk (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:172:36)
at retryTask (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1868:50)
at performWork (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1906:33)
at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1297:40)
at scheduleWork (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:52:25)
at pingTask (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1296:29)
at ping (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js:1309:40)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
and if i run it on a client rendered component i get:
fetch is not defined
at new ApolloError (index.js?2dcd:29:1)
at eval (QueryManager.js?f2a8:609:1)
at both (asyncMap.js?acdc:16:46)
at eval (asyncMap.js?acdc:9:57)
at new Promise (<anonymous>)
at Object.then (asyncMap.js?acdc:9:1)
at Object.eval [as next] (asyncMap.js?acdc:17:1)
at notifySubscription (module.js?4392:132:1)
at onNotify (module.js?4392:176:1)
at SubscriptionObserver.next (module.js?4392:225:1)
at eval (iteration.js?8787:4:50)
at Array.forEach (<anonymous>)
at iterateObserversSafely (iteration.js?8787:4:1)
at Object.next (Concast.js?c3b3:25:43)
at notifySubscription (module.js?4392:132:1)
at onNotify (module.js?4392:176:1)
at SubscriptionObserver.next (module.js?4392:225:1)
at eval (parseAndCheckHttpResponse.js?5a22:123:1)
At this point i’m just kind walking around in the dark as i can’t really wrap my head around what needs to be happening in order for the app to work with client rendered and server rendered components and where apollo client fits in to get my graphql data that i need.
Any suggestions or links to working examples for someone using Next JS 13 + AppDir + ApolloClient would be much appreciated!
Versions
System:
OS: Windows 10 10.0.22621
Binaries:
Node: 18.10.0 - D:\Program Files\nodejs\node.EXE
Yarn: 1.22.19 - D:\Program Files\nodejs\yarn.CMD
npm: 8.19.2 - D:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.22621.819.0), Chromium (108.0.1462.42)
npmPackages:
@apollo/client: ^3.7.2 => 3.7.2
Issue Analytics
- State:
- Created 9 months ago
- Reactions:1
- Comments:7 (2 by maintainers)
Top GitHub Comments
I did get it working with Apollo client but it invoked doing some weird things with wrapping the layout.tsx in a provider.tsx that lead flagged with “use client” and then whenever Apollo queries were called also using the same flag. This really missed the point of using the app dir for me so I ended up using
grapwhl-request
instead of Apollo client and everything works using just the app dir and RSCOn Tue, 20 Dec 2022, 19:17 Michiel Sikma, @.***> wrote:
I’m curious if there’s any information yet about what the recommended interface will look like for using Apollo with Next 13.
For example, this blog post for Next 12 talks about using the old
getStaticProps()
andgetServerSideProps()
api that is not supported in the /app dir.I realize this is all very new and I probably should not be using the /app dir yet if I want to use Apollo, but some thoughts on this would be appreciated since as far as I can tell there’s no real documentation on this yet.