Next.js on-demand Tag Revalidation with Custom Document Actions

By Soufiane

Alternative solution to Webhook revalidation

sanity.config.ts

'use client'

import { defineConfig, type DocumentActionComponent } from 'sanity'

import { originalActionWithRevalidate } from '@/sanity/lib/customDocumentActions/originalActionWithRevalidate'


export default defineConfig({
  // add this to your config setting
  document: {
    actions: (prev) =>
      prev.map((originalAction) =>
        originalActionWithRevalidate(originalAction),
      ),
  },
})

documentActions.ts

// @/sanity/lib/customDocumentActions/originalActionWithRevalidate

import { revalidateTagAction } from '@/app/server-actions'
import type { DocumentActionComponent, DocumentActionProps } from 'sanity'

export function originalActionWithRevalidate(
  originalAction: DocumentActionComponent,
) {
  return function originalResultWithRevalidate(props: DocumentActionProps) {
    const originalResult = originalAction(props)

    if (!originalResult) {
      return null
    }

    const shouldRevalidate =
      originalAction.action !== undefined &&
      ['publish', 'unpublish', 'delete', 'duplicate'].includes(
        originalAction.action,
      )

    return {
      ...originalResult,
      onHandle: async () => {
        if (typeof originalResult.onHandle === 'function') {
          originalResult.onHandle()
        }
        if (shouldRevalidate) {
          await fetch(`/api/revalidate?tag=${props.type}&secret=${'YOUR SECRET'}`)
        }
      },
    }
  }
}

route.ts

// @/app/api/revalidate/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { revalidateTag } from 'next/cache'

export async function GET(request: NextRequest) {
  const tag = request.nextUrl.searchParams.get('tag')
  const secret = request.nextUrl.searchParams.get('secret')
  
  try {
    if (secret !== 'YOUR SECRET') {
      return NextResponse.json(
        { success: false, error: 'Invalid secret' },
        { status: 401 },
      )
    }

    if (tag) {
      revalidateTag(tag)
      return NextResponse.json({ success: true, revalidatedTag: tag })
    } else {
      return NextResponse.json(
        { success: false, error: 'No tag provided' },
        { status: 400 },
      )
    }
  } catch (error) {
    console.error('Revalidate tag error: ', error)
    return NextResponse.json(
      { success: false, error: 'Internal server error' },
      { status: 500 },
    )
  }
}

The recommended approach for on-demand revalidation is to set up a Webhook. This method is effective, but it can be difficult to debug due to the blackbox nature of Webhooks.

As an alternative solution, the Document Action API can be used to customize actions with revalidation logic. However, there are two potential drawbacks to consider:

  1. The authorization process is more complex than the Webhook method, which comes with built-in security.

It's worth noting that the revalidation process does not leak any sensitive data. As a result, exposing the revalidate secret inside the Sanity Studio shouldn't be too critical.

When implementing revalidation, it's important to avoid a few common pitfalls:

  1. Ensure that you're not using the Sanity CDN client to fetch your tagged queries.
  2. Tag your fetch function with the same name as the document type.

Contributor

Other schemas by author