DEV Community

Marco Valsecchi
Marco Valsecchi

Posted on • Edited 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" })
    return
  }

  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)

Collapse
 
jamessingleton profile image
James Singleton

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

Collapse
 
valse profile image
Marco Valsecchi

Thanks, I updated the post ๐Ÿ˜‰

Collapse
 
beethoven profile image
Beto Garcia

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

Collapse
 
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.

Collapse
 
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.

Collapse
 
andreasstraub profile image
Andreas Straub

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

Collapse
 
zvirinz profile image
Dzmitry Sviryn

Very helpful! Thank, you ๐Ÿค“