Cache issues with Sanity and Next.js resolved using server-side rendering

11 replies
Last updated: Jan 7, 2021
Hello. I believe I have some sort of cache problem. I have a blog on Sanity and when I create or edit blog posts I can see the update instantly when I publish it on local but it takes close to 5-10min to appear on production. Ultimately it does appear but I'm wondering why this is happening.My blog is on Nextjs, hosted on Vercel.
AI Update

This is a classic caching issue when using Next.js with Sanity! The 5-10 minute delay you're experiencing is likely due to multiple caching layers working together. Let me break down what's happening and how to fix it.

Why This Happens

You're dealing with two separate caches:

  1. Sanity's CDN cache (apicdn.sanity.io) - The API CDN has its own caching policy that caches responses for performance
  2. Next.js/Vercel cache - If you're using ISR (Incremental Static Regeneration) or static generation, your pages are cached by Next.js

When you publish content, it updates in Sanity's Content Lake immediately (which is why you see it locally if you're querying without the CDN), but the cached versions take time to update.

The Solution: On-Demand Revalidation with Webhooks

The best approach is to set up on-demand revalidation using Sanity webhooks. This tells Vercel to immediately rebuild affected pages when you publish content. Here's how to set it up:

Step 1: Create a Revalidation API Route

In your Next.js app, create an API route. For the App Router, create app/api/revalidate/route.ts:

import { revalidateTag } from 'next/cache'
import { type NextRequest, NextResponse } from 'next/server'
import { parseBody } from 'next-sanity/webhook'

export async function POST(req: NextRequest) {
  try {
    const { body, isValidSignature } = await parseBody(
      req,
      process.env.SANITY_REVALIDATE_SECRET
    )
    
    if (!isValidSignature) {
      return new Response('Invalid signature', { status: 401 })
    }

    if (!body?._type) {
      return new Response('Bad Request', { status: 400 })
    }

    // Revalidate specific tags based on document type
    revalidateTag(body._type)
    
    return NextResponse.json({
      status: 200,
      revalidated: true,
      now: Date.now()
    })
  } catch (err) {
    console.error(err)
    return new Response('Internal Server Error', { status: 500 })
  }
}

For the Pages Router, create pages/api/revalidate.ts:

import type { NextApiRequest, NextApiResponse } from 'next'
import { parseBody } from 'next-sanity/webhook'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const { body, isValidSignature } = await parseBody(
      req,
      process.env.SANITY_REVALIDATE_SECRET
    )
    
    if (!isValidSignature) {
      return res.status(401).json({ message: 'Invalid signature' })
    }

    // Revalidate the path for this document
    await res.revalidate('/blog') // Adjust to your blog path
    
    return res.json({ revalidated: true })
  } catch (err) {
    return res.status(500).json({ message: 'Error revalidating' })
  }
}

Step 2: Set Up a Sanity Webhook

  1. Go to your Sanity project dashboard at sanity.io/manage
  2. Navigate to API β†’ Webhooks
  3. Create a new webhook with:
    • URL: https://your-site.vercel.app/api/revalidate
    • Dataset: Your dataset (usually production)
    • Trigger on: Create, Update, Delete
    • Filter: Optionally filter to specific document types like _type == "post"
    • Secret: Generate a secret and add it to your Vercel environment variables as SANITY_REVALIDATE_SECRET

Step 3: Disable Sanity CDN for Production Builds

In your Sanity client configuration, set useCdn: false to ensure you're getting fresh data during builds. The Sanity CDN and Next.js ISR can work at cross-purposes:

import { createClient } from 'next-sanity'

export const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2024-01-01',
  useCdn: false, // Disable CDN for fresh data
})

Modern Alternative: Sanity Functions

If you're on a Growth plan or higher, consider using Sanity Functions instead of webhooks. Functions run serverless code directly in Sanity when content changes, eliminating the need for external webhooks entirely. This is the modern, recommended approach for handling content events, as it provides better security, automatic scaling, and native integration with Sanity.

Quick Fix: Time-Based Revalidation

If you need a simpler solution right now, you can use time-based ISR revalidation in your data fetching:

// App Router with fetch
const posts = await fetch('https://your-project.api.sanity.io/...', {
  next: { revalidate: 60 } // Revalidate every 60 seconds
})

// Or with sanityFetch
const posts = await sanityFetch({
  query: POSTS_QUERY,
  revalidate: 60, // Revalidate every 60 seconds
})

This won't give you instant updates, but it'll reduce the delay from 5-10 minutes to 1 minute.

The on-demand revalidation approach is definitely the way to go for production sites where content authors expect immediate updates after publishing!

Oh. Do I have to create some sort of webhook to force a ploy every time there's a published update?
Depends how you're fetching your data from Sanity. If you're using getStaticProps without revalidate, you'll need to deploy on Vercel after making a change. If you're using revalidate or fetching via getServerSideProps, getPromise, etc., you shouldn't need to redeploy.
As for the 5-10 mins, that's probably due to
useCdn: true
when you configure your sanityClient.
Thanks for your reply
user A
. It's now working with a webhook from Vercel.
Nice! πŸŽ‰
It actually take 2 deploys to actually update the content with a webhook. Do you think this is because of the CDN?
Ah yes apparently. I'll try without to see how it goes.
You would want to set
useCdn: false
.
In case
useCdn: false
doesn't work: there might be a slight sync delay between when the webhook is fired and when the data is available from the API. You could consider calling a serverless function instead, which then calls the Vercel deploy hook after a short timeout to prevent having to do 2 deploys πŸ™‚
You might not need the webhook though, as Geoff suggested above.
Ah very true. I changed the request to be on every render server side and it works great. For some reason most example out there on Nextjs uses getStaticProps so I assumed it was best practice but it's much easier to do it per render to avoid having to do too much work on webhooks.
(Heads up: keep an eye on your API request usage statistics in case things need to be more frugal πŸ™‚)
Will do. Yes I can see it being a problem with higher traffic. We are fairly small for now so all good.

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.

Was this answer helpful?