Next.js Discord

Discord Forum

How to fix, nextjs pages refetching data after certain time passed when using graphql

Unanswered
Red imported fire ant posted this in #help-forum
Open in Discord
Avatar
Red imported fire antOP
I have this nextjs 14 project and every page using graphql to fetch the data. And i deployed it, after visiting some pages through the navbar menu page does load on the first as usual(which is correct i assume). And then i click on some other navbar menu items and go back to the one i have visited. This time it loads immediately, but sometimes(after certain time passed) and i revisit the page that i have already visited before, boom it refetches data. How do i fix this? is there any idea why this is happening.

42 Replies

Avatar
Black-throated Blue Warbler
need to see your fetch functions to determine how you are configuring the cacheing
"sometimes(after certain time passed) and i revisit the page that i have already visited before, boom it refetches data. "

Sounds like your cache is expiring šŸ˜‰
Avatar
Red imported fire antOP
@Black-throated Blue Warbler thank you for the reply, here is my graphql-request configuration:

import { GraphQLClient } from "graphql-request";
import { cache } from "react";
import { getSdk } from "@/gql"; // graphql-codegen

const CACHE_TIME = 60 * 60 * 1000; // 1 hour in milliseconds
const client = new GraphQLClient(
  ${process.env.NEXT_PUBLIC_BACKEND_URL}/graphql,
  {
    fetch: cache(async (url, params) =>
      fetch(url, { ...params, next: { revalidate: CACHE_TIME } }),
    ),
  },
);
const sdk = getSdk(client);
export default sdk;
Avatar
Black-throated Blue Warbler
hmm, well first issue is your cache time calculation, i think next revalidate uses seconds not miliseconds
so just put 3600 if you want 1hr
you could try setting the revlaidate for all requests on layout or page with:

export const revalidate = 3600 // revalidate at most every hour
Avatar
Red imported fire antOP
but isn't const revalidate = 3600 only work for GET request ?
Avatar
Red imported fire antOP
@Black-throated Blue Warbler thing is i didnt add any revalidate or custom fetch or cache before and project acts as i described. So then i tried to add those to fix this
Avatar
Black-throated Blue Warbler
oh right.. graphql use post by default
Avatar
Red imported fire antOP
yeah, šŸ˜¦ been struggling to find the solution to this
Avatar
Black-throated Blue Warbler
you have to setup your own cacheing most likely, i think you're on the right track but you need a function that tries to fetch the data from cache first
think you'd need a setup like this:

const client = new GraphQLClient(
  `${process.env.NEXT_PUBLIC_BACKEND_URL}/graphql`,
  {
    fetch: cache(async (url, params) => {
      const cacheKey = JSON.stringify({ url, params });
      const cachedResponse = getCachedResponse(cacheKey);

      if (cachedResponse) {
        return new Response(JSON.stringify(cachedResponse), { status: 200 });
      }

      const response = await fetch(url, { ...params, method: "POST" });
      const data = await response.json();

      setCachedResponse(cacheKey, data, CACHE_TIME);

      return new Response(JSON.stringify(data), { status: 200 });
    }),
  }
);


where it attempts fetching from your cache before firing the post request
Avatar
Red imported fire antOP
Where do i save the cache in here ? like setCachedResponse
Avatar
Black-throated Blue Warbler
Warning: Untested GPT response

import { GraphQLClient } from "graphql-request";
import { cache } from "react";
import { getSdk } from "@/gql"; // graphql-codegen

const CACHE_TIME = 60 * 60 * 1000; // 1 hour in milliseconds

// Create a cached fetch function
const cachedFetch = cache(async (url, params) => {
  const cacheKey = JSON.stringify({ url, params });
  const cachedResponse = getCachedResponse(cacheKey);

  if (cachedResponse) {
    return new Response(JSON.stringify(cachedResponse), { status: 200 });
  }

  const response = await fetch(url, { ...params, method: "POST" });
  const data = await response.json();

  setCachedResponse(cacheKey, data, CACHE_TIME);

  return new Response(JSON.stringify(data), { status: 200 });
});

const client = new GraphQLClient(
  `${process.env.NEXT_PUBLIC_BACKEND_URL}/graphql`,
  {
    fetch: cachedFetch,
  }
);

const sdk = getSdk(client);
export default sdk;

// In-memory cache utility functions
const cacheStorage = new Map();

const getCachedResponse = (key) => {
  const cached = cacheStorage.get(key);
  if (!cached) return null;
  const now = Date.now();
  if (now > cached.expiry) {
    cacheStorage.delete(key);
    return null;
  }
  return cached.value;
};

const setCachedResponse = (key, value, ttl) => {
  const now = Date.now();
  cacheStorage.set(key, { value, expiry: now + ttl });
};

export { getCachedResponse, setCachedResponse };
Avatar
Red imported fire antOP
Wait a minute, so post request does not have params and url will be always same right ?
Avatar
Black-throated Blue Warbler
post request should have some params like the body, log it and see
Avatar
Red imported fire antOP
Sorry but don't know why, returning new instance of Response with data or just data directly giving some strange error :

Error: GraphQL Error (Code: 200): {"response":{"error":"{\"data\":{\"HomePageContent\":{\"data\":{\"attributes\":{\"seo\":
yes you are correct, params does have body, so then cachekey works
One thing i couldn't figure out is what structure of the data fetch is returning, it seems when i remove the fetch overriding line it worked fine. But when i add it back this strange error thrown
Avatar
Black-throated Blue Warbler
seems like its already json and doesn't need to be stringified
try

  return new Response(data, { status: 200 });
Avatar
Red imported fire antOP
yup, tried that and still same
Avatar
Black-throated Blue Warbler
can i see your full code? are you throwing an error anywhere?
Avatar
Red imported fire antOP
import { GraphQLClient } from "graphql-request";
import { getSdk } from "@/gql";

const CACHE_TIME = 60 * 60 * 1000;

const cacheStorage = new Map();

const getCachedResponse = (key) => {
  const cached = cacheStorage.get(key);
  if (!cached) return null;
  const now = Date.now();
  if (now > cached.expiry) {
    cacheStorage.delete(key);
    return null;
  }
  return cached.value;
};

const setCachedResponse = (key, value, ttl) => {
  const now = Date.now();
  cacheStorage.set(key, { value, expiry: now + ttl });
};

const customFetch = async (url, params) => {
  const cacheKey = JSON.stringify({ url, params });
  const cachedResponse = getCachedResponse(cacheKey);

  if (cachedResponse) {
    return new Response(JSON.stringify(cachedResponse), { status: 200 });
  }

  const response = await fetch(url, { ...params, method: "POST" });

  // Check if response is not ok
  if (!response.ok) {
    throw new Error(`Network response was not ok: ${response.statusText}`);
  }

  // Attempt to parse response data
  const data = await response.json();

  // Check for GraphQL errors
  if (data.errors) {
    console.error("GraphQL errors:", data.errors);
    throw new Error(`GraphQL Error: ${JSON.stringify(data.errors)}`);
  }

  // Cache the response data
  setCachedResponse(cacheKey, data, CACHE_TIME);
  console.log("Set cache success");

  return new Response(data, {status: 200});
};

const client = new GraphQLClient(
  `${process.env.NEXT_PUBLIC_BACKEND_URL}/graphql`,
  {
    fetch: customFetch,
  },
);

const sdk = getSdk(client);

export default sdk;
Avatar
Black-throated Blue Warbler
well right here is throwing that error:

  // Check for GraphQL errors
  if (data.errors) {
    console.error("GraphQL errors:", data.errors);
    throw new Error(`GraphQL Error: ${JSON.stringify(data.errors)}`);
  }
instead of throwing the error just log it so we can read it
Avatar
Red imported fire antOP
I just checked and seems its not this error throwing
Avatar
Black-throated Blue Warbler
you sure you didn't cache that error? haha
comment out this part too for now

  if (cachedResponse) {
    return new Response(JSON.stringify(cachedResponse), { status: 200 });
  }
Avatar
Red imported fire antOP
haha, it was nt' cache does save, I just commented out and still same. but i am suspecting returning response has some issue because success log printed and then error raised

Set cache success { data: { componentHomePageContent: { data: [Object] } } } 
āØÆ Error: GraphQL Error (Code: 200): {"response":{"error":"[object Object]","status":200,"headers":
{}},"request":{"query":"query getHomePageContent {\n  componentHomePageContent {\n    data {\n     
 attributes {\n        seo {\n          title\n          meta_robots\n          meta_discription\n 
         link_canonical\n        }\n        Mission_block {\n    
Avatar
Black-throated Blue Warbler
what does it look like when you console log data
from:
const data = await response.json();
Avatar
Red imported fire antOP
and when i log the data
{ data: { componentHomePageContent: { data: [Object] } } }    
Avatar
Black-throated Blue Warbler
hmm well that looks valid, and i don't see an errors key
try just returning data
Avatar
Red imported fire antOP
Error: Cannot read properties of undefined (reading 'forEach')

And my page not even have any forEach šŸ˜„
Avatar
Black-throated Blue Warbler
I stopped using graphql for simple projects a while back, it just creates too much complexity. Is there a good reason for you to be using graphql on this site? just curious
Avatar
Red imported fire antOP
Backend only provides graphql
Avatar
Black-throated Blue Warbler
i'd say thats a pretty solid reason then, i think you'll have to do some digging on how to setup graphql-tag with the latest next.js cacheing. Unfortuantely i'm not very experienced with it.

When I was using graphql on my projects I just wrote my own fetch wrapper and didn't use any libraries
Avatar
Red imported fire antOP
OK thank you any way
Avatar
Black-throated Blue Warbler
example of what i mean by using graphql without library (with next.js custom fetch)

const { data } = await fetch(process.env.GRAPHQL_API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: `
    query getPosts {
      posts {
        edges {
          node {
            title
            excerpt
            slug
            date
          }
        }
      }
    }
  `,
    }),
    next: { revalidate: 10 },
  }).then((res) => res.json());
could slap a query in there just to see if it works with the revalidate like it should