apollo-client icon indicating copy to clipboard operation
apollo-client copied to clipboard

Can't reset cache from any API route in Next.js ssr.

Open robiot opened this issue 3 years ago • 3 comments

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:

  1. Create a next.js project
  2. Create a file named api.ts in src/lib and 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();
};
  1. Create a file named clearCache.ts in src/pages/api that calls the resetCache function from src/lib/api.ts
  2. Create a file named index.tsx in src/pages that uses the client from src/lib/api.ts in getServerSideProps to query some graphql data.
  3. Run next build and then next start
  4. Change the data that the client queries to something else
  5. Send a request to the api/clearCache defined in the frontend.
  6. 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 

robiot avatar Aug 03 '22 20:08 robiot

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 avatar Aug 05 '22 16:08 robiot

@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.

hwillson avatar Aug 09 '22 19:08 hwillson

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.ts which is there that the Apollo client and the wrappers around all the queries and their corresponding types are
  • src/pages/[[slug]].tsx which is a dynamic route that gets the page data, plus other data corresponding to the given URL path. It uses all the query wrappers from src/lib/api.ts. An example of getting data would be await 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

  1. Have a piece of code that is being ran every x minutes that executes client.resetStore()
  2. 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",
    },
  },
});
  1. Have a new client created on each request (as you suggested)
  2. 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.

robiot avatar Aug 09 '22 21:08 robiot

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!

alessbell avatar Jan 12 '23 19:01 alessbell