Apollo is not requesting graphql in ssr (through next.js) : requests only for the first query
See original GitHub issueIntended outcome:
I have volatile data that I want to be refreshed on each display
Actual outcome:
Queries are sent to the backend graphql server in development mode (next dev), but when the project is built and run (next start), only the first query is executed for each query: it seems the lib returns data from cache for all susequent requests.
I didn’t set any maxAge anywhere, so from what I understood, it should default to 0 and have all queries executed without caching.
How to reproduce the issue:
Here’s my configuration code for the apollo client:
/apollo/client.js
import {useMemo} from 'react'
import {ApolloClient, HttpLink, gql} from '@apollo/client'
import merge from 'deepmerge'
import {cache} from './cache'
import {isSSR} from "../constants/util";
import isEqual from 'lodash/isEqual'
import logger from "../lib/logger";
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
const typeDefs = gql`
extend type Query {
draftComplaints: [Complaint]
}
`;
let apolloClient
function createIsomorphLink() {
if (typeof window === 'undefined') {
const frontEndGraphQLUrl = process.env.NEXT_PUBLIC_FRONT_END_SSR_URL+'/api/graphql'
logger.debug("server side next links are pointing to ", frontEndGraphQLUrl)
return new HttpLink({
uri: frontEndGraphQLUrl,
headers: {ssr:true},
credentials: 'same-origin',
})
} else {
return new HttpLink({
uri: '/api/graphql',
headers: {ssr:false},
credentials: 'same-origin',
})
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(),
cache,
typeDefs
})
}
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
// get hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.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
_apolloClient.cache.restore(data)
}
// For SSG and SSR always create a new Apollo Client
if (isSSR()) return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(pageProps) {
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}
export function addApolloState(client, pageProps) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
In my endpoint, I’m adding the connected user to the header then forwarding the request to a backend server. My issue is that in ssr, this code is not called after the first display
/api/graphql.js import {getSession} from “next-auth/client”;
function isTrueStr(str) {
return str === true ||
(str != null &&
str.toString().toLowerCase() === 'true')
}
/**
* typeof window == 'undefined' doesn't work here, so client HttpLink was enriched by a header boolean field 'ssr'
* @param req
* @returns {Promise<{"content-type": string, "infra-token": *}>}
*/
async function prepareHeaders(req) {
let infraToken = ""
const ssr = isTrueStr(req.headers['ssr'])
if (ssr == true) {
//in SSR, we rely on client.query({context:{headers:{'infra-token':token}}})
infraToken = req.headers['infra-token']
} else {
//in client rendering, we rely on the user that made the request
const session = await getSession({req})
infraToken = session?.infraToken
}
// logger.debug("api/graphql: {ssr: ", ssr, " Request token:", infraToken)
let headers = {
'content-type': 'application/json',
'infra-token': infraToken ?? "",
ssr
}
//On server-side, we must wrap our headers in a node-fetch Header class instance
if (ssr) {
const Headers = (await import('node-fetch')).Headers
headers = new Headers(headers)
}
return headers;
}
export default async (_req, _res) => {
let headers = await prepareHeaders(_req);
const query = {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
headers,
redirect: 'follow', // manual, *follow, error
body: JSON.stringify({
operationName: _req.body.operationName,
variables: _req.body.variables,
query: _req.body.query
}) // body data type must match "Content-Type" header
};
const graphqlBaseUrl = process.env.NEXT_PUBLIC_BACK_END_URL
const res = await fetch(graphqlBaseUrl + '/api/graphql', query)
const json = await res.json()
console.log( "GRAPHQL QUERY: ", _req.body.operationName, " | variables: ", _req.body.variables )
console.log( "GRAPHQL RESPONSE: ", JSON.stringify(json) )
_res.statusCode = 200;
_res.setHeader('content-type', 'application/json');
_res.setHeader('cache-control', 'no-store, max-age=0');
_res.json(json)
_res.end()
};
export function prepareContext(session) {
return {
headers: {'infra-token': session?.infraToken ?? ""}
};
}
What I get in the output is only:
debug:["server side next links are pointing to ","http://next_server:3000/api/graphql"]
Which is the correct endpoint (so the SSR link is correctly created, but the API is not called), seems Apollo is caching the response of the API call, which is definitely not the intended behavior for my case. The issue is that I can’t find a documentation talking about next.js or apollo caching data in ssr
You might think there’s an issue with my cache code, but it doesn’t enter to it neither: the line
console.log("merging: existing ", existing.count, " incoming: ", incoming.count)
is not executed for the complaintConnection request
/apollo/cache.js import { InMemoryCache, makeVar } from ‘@apollo/client’;
export const draftComplaints = makeVar([])
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
draftComplaints: {
read() {
return draftComplaints();
}
},
complaintConnection: {
keyArgs: ['filter'],
merge(existing = [], incoming) {
console.log("merging: existing ", existing.count, " incoming: ", incoming.count)
if(existing?.pageInfo == null)
return incoming
if(existing.pageInfo.endCursor == incoming.pageInfo.endCursor)
return existing
return {
__typename: "ComplaintConnection",
count: existing.count,
edges: [...existing.edges, ...incoming.edges],
pageInfo: incoming.pageInfo
};
},
}
}
}
}
});
Versions
System: OS: Linux 5.11 Alpine Linux Binaries: Node: 14.17.3 - /usr/local/bin/node Yarn: 1.22.5 - /usr/local/bin/yarn npm: 6.14.13 - /usr/local/bin/npm npmPackages: @apollo/client: ^3.4.17 => 3.4.17 apollo-server-micro: ^3.5.0 => 3.5.0
Issue Analytics
- State:
- Created 2 years ago
- Reactions:2
- Comments:9 (1 by maintainers)
Top GitHub Comments
we use
fetchPolicy: isBrowser() ? 'network-only' : 'no-cache',
, that should be enough as server don’t need cache at all afaictSo I managed to fix the ssr bug by initiating my client with the following options:
Now, refreshing the page (ssr rendering), works.
But I still have an issue: when I rerender a page (client side), it doesn’t query the graphql endpoint.