On the first page load with the invalid access token, the response from meQuery is null, for some reason, it is not able to return a proper value from a query that is executed after refreshing tokens.
1st console log - refreshing tokens; 2nd console log - result from query in getServerSideProps
This issue occurs only on a server(ssr), on the client it works fine.
lib/apollo.tsx
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { FetchResult, NormalizedCacheObject } from '@apollo/client';
import {
ApolloClient,
ApolloLink,
HttpLink,
InMemoryCache,
Observable,
} from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { WebConfig } from '@commerce/config';
import merge from 'deepmerge';
import { isEqual } from 'lodash';
import type { GetServerSidePropsContext } from 'next';
import { useMemo } from 'react';
export const APOLLO_STATE_PROPERTY_NAME = '__APOLLO_STATE__';
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
const createApolloClient = (ctx?: GetServerSidePropsContext) => {
const httpLink = new HttpLink({
uri: WebConfig.apiUrl,
credentials: 'include',
});
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err.extensions?.code) {
case 'UNAUTHENTICATED': {
const observable = new Observable<FetchResult<Record<string, any>>>(
(observer) => {
(async () => {
try {
const data = await fetch(WebConfig.apiUrl, {
method: 'POST',
headers: {
Cookie: ctx?.req?.headers.cookie ?? '',
'content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation Refresh {
refresh
}
`,
}),
credentials: 'include',
});
ctx?.res?.setHeader(
'set-cookie',
data.headers.getSetCookie(),
);
console.log(ctx?.res?.getHeaders());
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
forward(operation).subscribe(subscriber);
} catch (error) {
observer.error(error);
}
})();
},
);
return observable;
}
}
}
}
});
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
Cookie: ctx?.req?.headers.cookie ?? '',
},
}));
return forward(operation);
});
return new ApolloClient({
ssrMode: typeof window === 'undefined',
ssrForceFetchDelay: 5000,
link: ApolloLink.from([errorLink, authLink, httpLink]),
cache: new InMemoryCache({}),
credentials: 'include',
defaultOptions: {
query: {
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
},
mutate: {
errorPolicy: 'all',
},
},
});
};
export function initializeApollo(
initialState: NormalizedCacheObject | null = null,
ctx: any = undefined,
) {
const client = apolloClient ?? createApolloClient(ctx);
// 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 = client.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// 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
client.cache.restore(data);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') {
return client;
}
// Create the Apollo Client once in the client
if (!apolloClient) {
apolloClient = client;
}
return client;
}
export function addApolloState(
client: ApolloClient<NormalizedCacheObject>,
pageProps: any,
) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROPERTY_NAME] = client.cache.extract();
}
return pageProps;
}
export const useApollo = (pageProps: any) => {
const state = pageProps[APOLLO_STATE_PROPERTY_NAME];
const store = useMemo(() => initializeApollo(state), [state]);
return store;
};
pages/index.tsx
import { addApolloState, initializeApollo } from '@commerce/web/apollo';
import { MeDocument, MeQuery, useMeQuery } from '@commerce/web/generated';
import { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const apolloClient = initializeApollo(null, ctx);
const { data } = await apolloClient.query<MeQuery>({
query: MeDocument,
});
console.log(data);
return addApolloState(apolloClient, {
props: {},
});
};
export function Index() {
const { data } = useMeQuery();
return <div>{JSON.stringify(data)}</div>;
}
export default Index;
I tried to investigate what was wrong and, unfortunately, didn't manage to find a solution
"@apollo/client": "^3.8.10",
"next": "13.4.4",