Issue with fetching/caching data from Sanity.io in Next.js app deployed on Vercel.
I can see the issue! The problem is your useCdn: true setting combined with how you're fetching data. When you use Sanity's CDN, you're getting cached data that can be stale, and the next: { revalidate: 10 } option only controls Next.js's cache layer—not Sanity's CDN cache.
The Root Cause
Your client is configured with useCdn: true, which means you're querying Sanity's CDN instead of the live API. The CDN serves cached content that can be several minutes old (typically around 10 minutes). In development, you might not notice this because of how Next.js handles caching in dev mode, but in production on Vercel, you're getting stale data from the CDN layer before Next.js's revalidation even comes into play.
The next: { revalidate: 10 } you're passing only controls Next.js's Data Cache, not Sanity's CDN cache. So even if Next.js revalidates every 10 seconds, it's still fetching from Sanity's CDN which might be serving content that's minutes old.
The Solution
You have a few options depending on your needs:
Option 1: Disable the CDN (Simplest Fix)
For a simple website where you want guaranteed fresh data, just turn off the CDN:
// lib/sanity.ts
export const client = createClient({
projectId,
dataset,
apiVersion,
useCdn: false, // Query the API directly for fresh data
perspective: "published",
});This queries Sanity's API directly instead of the CDN, ensuring you always get fresh published content. The next: { revalidate: 10 } will then work as expected to control Next.js's cache.
Option 2: Use Tag-Based Revalidation (Recommended for Production)
A more sophisticated approach is to use tag-based revalidation with on-demand revalidation. This lets you keep the CDN benefits while ensuring fresh data when content changes:
// lib/sanity.ts
export async function sanityFetch<QueryResponse>({
query,
tags,
}: {
query: string;
tags?: string[];
}): Promise<QueryResponse> {
return client.fetch<QueryResponse>(query, {}, {
next: {
tags: tags || [],
}
});
}
// app/layout.tsx
export async function generateMetadata(): Promise<Metadata> {
const layoutMetadata = await sanityFetch<TYPEMetadata>({
query: META_QUERY,
tags: ['metadata'],
});
const metadata = layoutMetadata.meta;
return {
title: metadata.title,
};
}Then set up a Sanity Function (recommended) or webhook to call Next.js's revalidation endpoint when your metadata changes in Sanity. This gives you the best of both worlds: CDN speed + instant updates when content changes.
Option 3: Use the Live Content API (Modern Approach)
If you want real-time updates without managing webhooks, consider using the Live Content API which is now GA. This provides instant content updates with CDN support built-in.
Why Development Worked
In development mode, Next.js doesn't aggressively cache, and your browser might not be respecting cache headers the same way Vercel's production environment does. This masked the CDN staleness issue you're experiencing in production.
Quick Test
To verify this is the issue, temporarily set useCdn: false and redeploy. If your data updates immediately, you've confirmed the CDN was serving stale content.
For a simple website, useCdn: false is perfectly fine and gives you the straightforward behavior you're looking for. The CDN is more beneficial when you have high traffic and can implement proper cache invalidation strategies.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.