apollo-client
apollo-client copied to clipboard
Can't reset cache from any API route in Next.js ssr.
src/lib/api.ts
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
uri: "https://something.com/graphql",
cache: new InMemoryCache({}),
});
export const resetCache = async () => {
// await client.cache.reset(); // This doesn't work either
await client.resetStore();
};
// This works
setInterval(async () => {
console.log("Clearing cache");
await resetCache();
}, 1 * 60_000); // 1 minute
src/pages/api/clearCache.ts
// Minimal example without any auth, please do not use this in prod.
import { resetCache } from "@lib/api";
import { NextApiHandler } from "next";
const handler: NextApiHandler = async (req, res) => {
if (req.method !== "POST") {
res.status(405).send({ message: "Method not allowed" });
return;
}
await resetCache();
res.status(200).json({ message: "Ok" });
};
export default handler;
Intended outcome:
The cache should be cleared when being ran from any Next.js api route.
Actual outcome:
The cache is not cleared when being ran from any Next.js api route.
How to reproduce the issue:
- Create a next.js project
- Create a file named
api.tsinsrc/liband put this content in there:
export const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
uri: "https://something.com/graphql",
cache: new InMemoryCache({}),
});
export const resetCache = async () => {
// await client.cache.reset(); // This doesn't work either
await client.resetStore();
};
- Create a file named
clearCache.tsinsrc/pages/apithat calls theresetCachefunction fromsrc/lib/api.ts - Create a file named
index.tsxinsrc/pagesthat uses the client fromsrc/lib/api.tsin getServerSideProps to query some graphql data. - Run
next buildand thennext start - Change the data that the client queries to something else
- Send a request to the api/clearCache defined in the frontend.
- Refresh the page and see that the old data is still there.
Versions
System:
OS: Linux 5.18 Arch Linux
Binaries:
Node: 17.9.0 - /usr/local/bin/node
Yarn: 1.22.19 - ~/.yarn/bin/yarn
npm: 8.5.5 - /usr/local/bin/npm
Browsers:
Chrome: 100.0.4896.60
Firefox: 101.0.1
npmPackages:
@apollo/client: ^3.5.10 => 3.5.10
This is probably because it resets the cache on a copy of the client. I'll need to figure out a way to get a direct reference to the client from another file.
@robiot I'm curious to learn more about your use case here. When using SSR we currently recommend creating an entirely new instance of Apollo Client on each request, to help reset the cache (and avoid potentially showing sensitive cached query results from a previous request). With this approach you shouldn't need to call resetStore.
Oh yeah @hwillson. So this is a bit of a special use case. To get a clearer view of the whole subject, I'm going to quickly go through why I am trying to achieve this, my solutions, and a bit about the project.
The project consists of two parts, the backend (PHP) and the frontend (Next.js). The backend serves as a content management system and has a Graphql endpoint that allows getting among other data, the pages. The frontend queries that endpoint, and gets all the needed data for the given path (ex. http://localhost:3000/home) from getServerSideProps in a dynamic route [[slug]].tsx. A page with the data from the server-side props is then returned back from the server.
Project structure
Without going into too much detail, the frontend consists of:
src/lib/api.tswhich is there that the Apollo client and the wrappers around all the queries and their corresponding types aresrc/pages/[[slug]].tsxwhich is a dynamic route that gets the page data, plus other data corresponding to the given URL path. It uses all the query wrappers fromsrc/lib/api.ts. An example of getting data would beawait getPageBySlug(slug). It then returns all the data from the getServerSideProps and it's passed to the component parameters. There the data gets parsed and components render out and...
The backend has all the content management system stuff, plus the Graphql endpoint.
The problem
Nothing will change in the frontend when updating data in the content management system.
The solutions
- Have a piece of code that is being ran every x minutes that executes
client.resetStore() - Specify the no-cache policy
const client: ApolloClient = new ApolloClient({
uri: "http://something.com/graphql,
cache: new InMemoryCache({}),
defaultOptions: {
watchQuery: {
fetchPolicy: "no-cache",
errorPolicy: "ignore",
},
query: {
fetchPolicy: "no-cache",
errorPolicy: "all",
},
},
});
- Have a new client created on each request (as you suggested)
- The solution that I was trying to achieve and created this issue for.
Not using caching is possible, but it's not optimal for this use case. Since it is running multiple queries on each request, caching them would speed up the initial page response time significantly.
My nonworking solution
An idea that I had to solve this, was to have an endpoint on the frontend to which the backend sends a request whenever any data changed. Then I got into this problem that I can't get a direct reference to the initialized ApolloClient but instead gets a copy of it. I even tried creating a function in the api.ts file that calls client.resetStore() and called that custom function instead, but it still didn't work.
If a solution is found to the import copy problem, this solution would be really awesome.
Hi @robiot 👋 Thanks for sharing your approach here! This looks like a question for the community, so you may want to consider re-posting to our Discourse forum or Discord server. If you have a runnable reproduction of a suspected bug in the future, please feel free to open a new issue. Thanks!