DEV Community

Marco Valsecchi
Marco Valsecchi

Posted on • Updated on


Next.js on-demand ISR by Sanity GROQ-powered webhooks

With the latest Next.js 12.1 version finally we got one of the most powerful missing feature: on-demand ISR 😮!

Thanks to this you can revalidate your SSG pages on the fly without rebuild all the site or without waiting the scheduled time set in the revalidate option as we were used to until today.

I love Sanity as headless CMS for its user friendly studio and for the power of its tools and plugins; I used to install the sanity-plugin-vercel-deploy plugin, very useful to update my SSG sites hosted on Vercel but this was meaning trigger a new build and redeploy all the entire site (I never used ISR with its revalidate option because, on bigger sites, the build cost would be too high).

One of the Sanity great feature is how they manage webhooks: you can trigger an URL after you edit your data specifying which document type and what send as payload, simply querying your database with its GROQ query language!

Now you can add a new API URL in your Next.js web app to revalidate your page content on-demand and request it by the Sanity webhook trigger 🤩.

For example, in your blog, imagine to fix a post typo on the Sanity studio and, after less a second, see your edit live. Cool right? First of all you need to add a new API endpoint on your web app, adding a file like this on the pages/api folder (yes I 🥰 TypeScript too):

import { isValidRequest } from "@sanity/webhook"
import type { NextApiRequest, NextApiResponse } from "next"

type Data = {
  message: string

const secret = process.env.SANITY_WEBHOOK_SECRET

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
  if (req.method !== "POST") {
    console.error("Must be a POST request")
    return res.status(401).json({ message: "Must be a POST request" })

  if (!isValidRequest(req, secret)) {
    res.status(401).json({ message: "Invalid signature" })

  try {
    const {
      body: { type, slug },
    } = req

    switch (type) {
      case "post":
        await res.revalidate(`/news/${slug}`)
        return res.json({ message: `Revalidated "${type}" with slug "${slug}"` })

    return res.json({ message: "No managed type" })
  } catch (err) {
    return res.status(500).send({ message: "Error revalidating" })

Enter fullscreen mode Exit fullscreen mode

In this function I accept a POST request with a type and a slug as payload; there are three main points to pay attention:

  1. validate the Sanity webhook secret, so we are safe to accept the request
  2. call the revalidate method passing the path that we need to purge as the argument
  3. set the useCdn Sanity client option to false to allow to get fresh content after the revalidate call (the webhook is too fast 😅)

This is how I set my Sanity webhook:
Sanity webhook settings

I chosen to send the document type on the payload so I can manage the revalidate with a unique endpoint but you are free to follow your best needs.

This new Next.js feature is the beginning of a new era:

  • you don't need to SSR your pages but keep them on your CDN, with no power consumption 💚 and #JAMstack compliant
  • updates will be immediately online, no more building wait time
  • your editors will be happy to preview, publish and check contents on the fly!

Thanks Vercel 🔼!

Top comments (7)

jamessingleton profile image
James Singleton

You should update this now that it is no longer unstable.

valse profile image
Marco Valsecchi

Thanks, I updated the post 😉

beethoven profile image
Beto Garcia

Greate guide, posted it on nextjs channel of sanity-io-land slack. Tks!

iankduffy profile image
Ian Duffy

Great guide, I have found this doesn't yet work on netlify at the moment, just Incase people wonder why they can't get it to work.

hmaina profile image
Hussaini Maina

Exactly what I'm implementing in a blog, I can't believe somebody already wrote a blog post about this. I'll surely try out your method.

andreasstraub profile image
Andreas Straub

Thx for sharing. Really great guide. I'll test is also in my next project

zvirinz profile image
Dzmitry Sviryn

Very helpful! Thank, you 🤓