CoursesControlling cached content in Next.jsPath-based revalidation
Track
Work-ready Next.js

Controlling cached content in Next.js

Log in to watch a video walkthrough of this lesson
Video thumbnail

Surgically revalidate individual post pages by their path when updates are made to their document in Sanity Studio.

Log in to mark your progress for each Lesson and Task

Next.js provides a function revalidatePath, which will purge the cache for fetches that took place on a specific URL.

Implementing this feature is a massive win for your business stakeholder groups and content authors.

  • If post routes are revalidated individually, you can safely give them a much longer time-based revalidation setting – perhaps even infinite – significantly reducing Sanity request volume and server bandwidth.
  • Content authors can press "publish" on a document, which automatically revalidates the cache for just that page, and see their updates published instantly.

Currently, each post document in Sanity is rendered on a unique route in Next.js. Because of this 1:1 relationship between document and route, revalidating a cached post is straightforward.

The goal is to purge the cache for a post whenever that post is edited – fixing the "typo problem" highlighted in the previous lesson. Ideally, this should happen automatically, and GROQ-powered webhooks make this possible.

GROQ-powered webhooks allow you to automate side effects from the Content Lake based on any mutation to a document in a dataset.

While you could automate a function from the Studio to call one of Next.js' revalidate functions, triggering this from a webhook is much safer. It is guaranteed to be called from Sanity infrastructure – not in the browser because of a client interaction. It's also safer with automatic retries should the operation fail.

So, for a little extra setup, you get much more reliability.

See more about GROQ-powered webhooks in the documentation.

The code below is for a new API route in your web application designed to be requested by a GROQ-powered webhook. It will:

  1. Only handle a POST request.
  2. Confirm that the request came from a Sanity GROQ-powered webhook.
  3. Retrieve the body from the request.
  4. Retrieve the path attribute from the request body and revalidate it.
Rename your .env file to .env.local, as it will now contain secrets
Update your .env.local file with a new secret to secure the route. It can be any random string
.env.local
SANITY_REVALIDATE_SECRET=<any-random-string>

You will also add this string to the GROQ-powered webhook you set up in this lesson.

Create a new route to execute revalidatePath
src/app/api/revalidate/path/route.ts
import { revalidatePath } from 'next/cache'
import { type NextRequest, NextResponse } from 'next/server'
import { parseBody } from 'next-sanity/webhook'
type WebhookPayload = { path?: string }
export async function POST(req: NextRequest) {
try {
if (!process.env.SANITY_REVALIDATE_SECRET) {
return new Response(
'Missing environment variable SANITY_REVALIDATE_SECRET',
{ status: 500 },
)
}
const { isValidSignature, body } = await parseBody<WebhookPayload>(
req,
process.env.SANITY_REVALIDATE_SECRET,
)
if (!isValidSignature) {
const message = 'Invalid signature'
return new Response(JSON.stringify({ message, isValidSignature, body }), {
status: 401,
})
} else if (!body?.path) {
const message = 'Bad Request'
return new Response(JSON.stringify({ message, body }), { status: 400 })
}
revalidatePath(body.path)
const message = `Updated route: ${body.path}`
return NextResponse.json({ body, message })
} catch (err) {
console.error(err)
return new Response((err as Error).message, { status: 500 })
}
}

This route confirms the value of the SANITY_REVALIDATE_SECRET environment variable and handles any invalid requests to the route.

Because this API route is only configured to handle requests with a POST method, visiting it in your browser (which performs a GET request) will not work.

Let's configure the webhook to do that for us.

In this lesson, you'll only configure revalidatePath for a single static path. Still, it can also revalidate an entire dynamic path—like /(frontend)/posts/[slug]—see the Next.js documentation on revalidatePath for more details.

A GROQ-powered webhook allows Content Lake to perform requests on your behalf whenever a mutation on a dataset is made. This is ideal for content operations like on-demand cache invalidation and will power a workflow where even a page set to be indefinitely cached can be purged and repopulated on demand.

A tricky part of developing GROQ-powered webhooks is that even when making content changes in your Sanity Studio's local development environment, webhooks will fire remotely in the Content Lake – but the Content Lake cannot request API routes in your local development environment.

You'll need to share your local URL with the world. Several services can help you do this. Perhaps the simplest and best-known is Ngrok.

Create a new free account at Ngrok if you do not already have one.
Once logged in, complete any installation instructions for your machine
Run the following command below to share your local Next.js remotely
ngrok http http://localhost:3000

Now, in the terminal, you should see many details about your account, version, etc. Along with a "Forwarding" URL which looks something like this:

https://8067-92-26-32-42.ngrok-free.app

Open that URL in your browser to see your local Next.js app. You can click links – and even open /studio (though you will need to add a CORS origin to interact with it).

Now, you have a remote URL of your local development environment for a GROQ-powered webhook to request.

Fortunately, we have prepared a webhook template to add to your Project. It has most of the settings preconfigured. You'll just need to update a few that are unique to you:

Update the URL to use your ngrok URL
Click "Apply Webhook" and select your project, apply to all datasets
Click "Edit Webhook" on the next screen, scroll to the bottom, and add the same "Secret" you added to your .env file
Click "Save"

You're now ready to publish changes in Sanity Studio to automatically revalidate a document's cached page in your web application.

The "Path" being revalidated is set within the "Projection" of the webhook using the GROQ function select(). As your application grows, it could be extended to include other unique paths based on the document type or any other logic.

With all the machinery in place, you can test that what you've set up works.

Visit an individual post page in your application, and check the terminal first to ensure it was a cache HIT.

If it is a cache MISS, reload the page, and you should see it cached for the following request.

Open that post within your Sanity Studio at http://localhost:3000/studio, change the title and publish it.

Almost instantly after publishing, in your terminal you should see a POST request which was automatically made from the GROQ-powered webhook:

POST /api/revalidate/path 200 in 3540ms
Reload the same individual post page again. You should see a cache MISS in the terminal and your updated title on the page.

If you reload the page again, it should be cached for the next request.

Are you still seeing stale data? Currently, GROQ-powered webhooks fire when the mutation is made via the Sanity API, but before the Sanity CDN is updated. Your request may have been made in the brief period in between, and the Next.js repopulated with stale data from the Sanity CDN.

If you encounter this situation, there are ways to mitigate it.

Within your API route to revalidate the path, the parseBody function from next-sanity takes a third argument to add a short delay before proceeding.

src/app/api/revalidate/path/route.ts
const {isValidSignature, body} = await parseBody<WebhookPayload>(
req,
process.env.SANITY_REVALIDATE_SECRET,
true
)

Alternatively, if you ensure Next.js caches every fetch, you could change the Sanity Client configuration to never use the CDN by setting useCdn: false.

Beware of the potential pitfalls of constantly querying the Sanity API directly. You don't want your application to go viral with uncached requests to the Sanity API.

What you have set up now is pretty great! Individual post pages have long-lived cache times and are revalidated automatically on demand when changes are published.

There are two new problems to solve:

  1. The post index page isn't being revalidated when a post changes, so an update to the title does not appear.
  2. The post index and individual post pages show author and category document values. If those documents are updated, we need to revalidate any component that renders those.

Fortunately, Next.js also offers "tag-based revalidation" for "update once, revalidate everywhere" content operations, and you'll set it up in the next lesson.

Courses in the "Work-ready Next.js" track