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

Controlling cached content in Next.js

Lesson
6

Tag-based revalidation

Assign tags to queries to revalidate the cache of many paths by targeting any individual tag.
Log in to mark your progress for each Lesson and Task

So far, the focus has been on revalidating individual post pages when a post changes. But with the current content model, more than just post-type document fields are queried—and there's more than one route where those responses are rendered.

A post-type document could contain many category references and a single author reference. Ideally, a content author editing any of these document types should impact every route where that content is rendered.

This is made possible with revalidateTag.

Time-based and tag-based revalidation cannot be used together. This is why your sanityFetch function is configured to ignore the revalidate parameter if tags are provided.

In this lesson, you'll remove time-based revalidation from your existing queries instead of tag-based. You now know how to implement time and path-based revalidation in the future for instances where it is applicable.

When performing a fetch, tags can be an array of strings of any value. It's a standard convention to use the Sanity document types expected to be in the response.

For the posts index, add tags for the three document types that provide data for the response. If any documents of these types change, you'll want to revalidate any page that renders them.

Update your fetch in the post-index route
src/app/(frontend)/posts/page.tsx
const posts = await sanityFetch({
query: POSTS_QUERY,
tags: ['post', 'author', 'category'],
})

You can be more granular for an individual post page. You don't need to revalidate every post page because one has post changed. Thankfully, the dynamic route provides us with a unique identifier for this post—its slug—so you can use it for this page's cache tags.

Update your fetch in the post route
src/app/(frontend)/posts/[slug]/page.tsx
const post = await sanityFetch({
query: POST_QUERY,
params,
tags: [`post:${params.slug}`, 'author', 'category'],
})

Once again, you will need to create an API route to accept a request from a GROQ-powered webhook and perform the revalidation.

Create a new API route for revaliateTag
src/app/api/revalidate/tag/route.ts
import { revalidateTag } from 'next/cache'
import { type NextRequest, NextResponse } from 'next/server'
import { parseBody } from 'next-sanity/webhook'
type WebhookPayload = {
tags: 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,
true,
)
if (!isValidSignature) {
const message = 'Invalid signature'
return new Response(JSON.stringify({ message, isValidSignature, body }), {
status: 401,
})
} else if (!Array.isArray(body?.tags) || !body.tags.length) {
const message = 'Bad Request'
return new Response(JSON.stringify({ message, body }), { status: 400 })
}
body.tags.forEach((tag) => {
revalidateTag(tag)
})
return NextResponse.json({ body })
} catch (err) {
console.error(err)
return new Response((err as Error).message, { status: 500 })
}
}
If you wish to delay the revalidation due to the Sanity CDN, include the third argument in parseBody, highlighted above.
Instructions for how to test webhooks during local development are in the previous lesson: Path-based revalidation

Once again, we have prepared a webhook template for 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 automatically revalidate your posts index and individual post pages in your web application simply by changing a post, author, or category document in Sanity Studio.

According to the Next.js documentation, revalidateTag only invalidates the cache when the path is next visited. This means calling revalidateTag with a dynamic route segment will not immediately trigger many revalidations at once.
If you still have the path-based revalidation webhook enabled, disable it in Manage.

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

Visit the post index page at http://localhost:3000/posts, 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 next request.

Open any category or author-type document within your Sanity Studio at http://localhost:3000/studio, change any field and publish.

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/tag 200 in 3540ms
Reload the same individual post page again.

If you reload the page again, it should be cached for the next request. You should see a cache MISS in the terminal, your updated title on the post-index and the individual post page.

Are you still seeing stale data? The previous lesson includes instructions on how to mitigate the time between a webhook firing and the CDN being repopulated: Path-based revalidation

With that, you're all cached up with somewhere to go. Let's review it in the final lesson.

You have 11 uncompleted tasks in this lesson
0 of 11