How I Fixed the Hashnode GraphQL API Stale Cache Bug (Stellate CDN)
Source: Dev.to

If you’re using Hashnode’s GraphQL API to fetch your blog posts for a custom frontend (like a React or Next.js portfolio), you’ve probably run into this incredibly frustrating issue: You publish a new post on Hashnode, but it doesn’t show up on your website.
You check the API payload, and it’s serving a stale list of posts. The new post is completely missing.
I spent hours debugging this, trying every cache‑busting trick in the book. Here’s what didn’t work, why it failed, and the actual simple solution.
The Setup
My portfolio runs on React (Vite) and uses Hashnode as a headless CMS. I fetch posts using fetch with the official Hashnode GraphQL endpoint: https://gql.hashnode.com.
const res = await fetch('https://gql.hashnode.com', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: POSTS_QUERY, variables }),
});
When I added a new post, my local dev server still only showed the old posts.
The Failed Attempts
Hashnode uses Stellate, a powerful GraphQL Edge CDN. Stellate sits between your frontend and Hashnode’s database, caching responses to make the API blazing fast. However, its caching mechanism is exceptionally aggressive.
Here are the standard tricks I tried to bypass the cache, all of which failed:
- Setting standard HTTP headers: Adding
Cache-Control: no-cacheandPragma: no-cacheto thefetchcall. Stellate ignores these from the client. - URL Query Params: Appending a timestamp
?_t=${Date.now()}to the endpoint URL. Since this is aPOSTrequest, Stellate keys the cache off the request body, not just the URL. - GraphQL
extensions: Addingextensions: { cacheKey: Date.now() }to the JSON body. Stellate normalization strips this out. - Unknown GraphQL Variables: Injecting
_cacheBust: Date.now()into thevariablesobject. Stellate strips unknown variables. - GraphQL Comments: Injecting
# ${Date.now()}directly into the query string. Stellate parses and normalizes the query string, stripping comments before hashing the cache key.
No matter what I did, inspecting the network tab always revealed the same mocking response header: gcdn-cache: HIT.
The Real Issue: Stellate Needs the id Field
The root cause isn’t that Stellate is ignoring your cache‑busting hacks; it’s that Stellate doesn’t know the data is stale.
Stellate automatically invalidates its cache when backend data changes (mutations). But to do this effectively across complex GraphQL graphs, it relies on a core concept: tracking Node IDs.
If your query doesn’t ask for the id of the entities it’s fetching, Stellate cannot trace the cached data back to the actual database records. When Hashnode updates its database, Stellate’s purge mechanism fires, but if your cached query didn’t include IDs, Stellate doesn’t know to invalidate that specific query.
This is a well‑known issue internally at Hashnode — they even created an ESLint plugin (require-id-when-available) for their own engineers to prevent it!
The Solution
The fix is almost disappointingly simple. You must include the id field in all your GraphQL queries and fragments related to Hashnode.
Original query
query Publication($host: String!, $first: Int!) {
publication(host: $host) {
posts(first: $first) {
edges {
node {
slug
title
brief
publishedAt
}
}
}
}
}
Updated query (with id)
query Publication($host: String!, $first: Int!) {
publication(host: $host) {
posts(first: $first) {
edges {
node {
id # <-- THE MAGIC FIX
slug
title
brief
publishedAt
}
}
}
}
}
Make sure to add id wherever you query entities: posts, post, seriesList, etc.
Once I updated my queries and refreshed, the CDN properly registered the unique records. New posts now invalidate the cache automatically, and the API returns fresh data immediately upon publishing.
Takeaway: When working with GraphQL APIs behind Edge CDNs like Stellate, always fetch the id. It’s not just good practice; it’s the anchor for their entire caching strategy.