diff --git a/examples/cms-sanity/.env.local.example b/examples/cms-sanity/.env.local.example
index 6bc5dc43020b..a246ea991e7c 100644
--- a/examples/cms-sanity/.env.local.example
+++ b/examples/cms-sanity/.env.local.example
@@ -2,3 +2,4 @@ NEXT_PUBLIC_SANITY_PROJECT_ID=
NEXT_PUBLIC_SANITY_DATASET=
SANITY_API_TOKEN=
SANITY_PREVIEW_SECRET=
+SANITY_STUDIO_REVALIDATE_SECRET=
diff --git a/examples/cms-sanity/README.md b/examples/cms-sanity/README.md
index e7b5d268d7f9..d74846361b8e 100644
--- a/examples/cms-sanity/README.md
+++ b/examples/cms-sanity/README.md
@@ -6,6 +6,7 @@ You'll get:
- Sanity Studio running on localhost
- Sub-second as-you-type previews in Next.js
+- [On-demand revalidation of pages](https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta) with [GROQ powered webhooks](https://www.sanity.io/docs/webhooks)
## Demo
@@ -15,7 +16,7 @@ You'll get:
Once you have access to [the environment variables you'll need](#step-4-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
-[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&project-name=cms-sanity&repository-name=cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env)
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&project-name=cms-sanity&repository-name=cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET,SANITY_STUDIO_REVALIDATE_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env)
### Related examples
@@ -80,6 +81,7 @@ Then set each variable on `.env.local`:
- `NEXT_PUBLIC_SANITY_DATASET` should be the `dataset` value from the `sanity.json` file created in step 2 - defaults to `production` if not set.
- `SANITY_API_TOKEN` should be the API token generated in the previous step.
- `SANITY_PREVIEW_SECRET` can be any random string (but avoid spaces), like `MY_SECRET` - this is used for [Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode).
+- `SANITY_STUDIO_REVALIDATE_SECRET` should be setup the same way as `SANITY_PREVIEW_SECRET` - this is used for [on-demand revalidation](https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta) with [webhooks](https://www.sanity.io/docs/webhooks).
Your `.env.local` file should look like this:
@@ -88,6 +90,7 @@ NEXT_PUBLIC_SANITY_PROJECT_ID=...
NEXT_PUBLIC_SANITY_DATASET=...
SANITY_API_TOKEN=...
SANITY_PREVIEW_SECRET=...
+SANITY_STUDIO_REVALIDATE_SECRET=...
```
### Step 5. Prepare the project for previewing
@@ -193,9 +196,25 @@ To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [
Alternatively, you can deploy using our template by clicking on the Deploy button below.
-[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&project-name=cms-sanity&repository-name=cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env)
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&project-name=cms-sanity&repository-name=cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET,SANITY_STUDIO_REVALIDATE_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env)
-#### Next steps
+### Step 11. Setup Revalidation Webhook
+
+- Open your Sanity manager, go to **API**, and **Create new webhook**.
+- Set the **URL** to use the vercel app url from [Step 10](#step-10-deploy-on-vercel) and append `/api/revalidate`, for example: `https://cms-sanity.vercel.app/api/revalidate`
+- Set the **Trigger on** field to
+- Set the **Filter** to `_type == "post" || _type == "author"`
+- Set the **Secret** to the same value you gave `SANITY_STUDIO_REVALIDATE_SECRET` earlier.
+- Hit **Save**!
+
+#### Testing the Webhook
+
+- Open the Deployment function log. (**Vercel Dashboard > Deployment > Functions** and filter by `api/revalidate`)
+- Edit a Post in your Sanity Studio and publish.
+- The log should start showing calls.
+- And the published changes show up on the site after you reload.
+
+### Next steps
-- Invalidate your routes in production [on-demand](https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta) with GROQ powered webhooks
- Mount your preview inside the Sanity Studio for comfortable side-by-side editing
+- [Join the Sanity community](https://slack.sanity.io/)
diff --git a/examples/cms-sanity/lib/config.js b/examples/cms-sanity/lib/config.js
index b4b0101e5494..c28d1f9c1b86 100644
--- a/examples/cms-sanity/lib/config.js
+++ b/examples/cms-sanity/lib/config.js
@@ -2,10 +2,13 @@ export const sanityConfig = {
// Find your project ID and dataset in `sanity.json` in your studio project
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
- useCdn: process.env.NODE_ENV === 'production',
+ useCdn: process.env.NODE_ENV !== 'production',
// useCdn == true gives fast, cheap responses using a globally distributed cache.
- // Set this to false if your application require the freshest possible
- // data always (potentially slightly slower and a bit more expensive).
+ // When in production the Sanity API is only queried on build-time, and on-demand when responding to webhooks.
+ // Thus the data need to be fresh and API response time is less important.
+ // When in development/working locally, it's more important to keep costs down as hot reloading can incurr a lot of API calls
+ // And every page load calls getStaticProps.
+ // To get the lowest latency, lowest cost, and latest data, use the Instant Preview mode
apiVersion: '2021-03-25',
// see https://www.sanity.io/docs/api-versioning for how versioning works
}
diff --git a/examples/cms-sanity/package.json b/examples/cms-sanity/package.json
index 8b576f61ae8c..529122e867f4 100644
--- a/examples/cms-sanity/package.json
+++ b/examples/cms-sanity/package.json
@@ -8,6 +8,7 @@
"dependencies": {
"@portabletext/react": "^1.0.3",
"@sanity/image-url": "^1.0.1",
+ "@sanity/webhook": "^1.0.2",
"classnames": "2.3.1",
"date-fns": "2.28.0",
"next": "latest",
diff --git a/examples/cms-sanity/pages/api/revalidate.js b/examples/cms-sanity/pages/api/revalidate.js
new file mode 100644
index 000000000000..9e2af5b3ebbd
--- /dev/null
+++ b/examples/cms-sanity/pages/api/revalidate.js
@@ -0,0 +1,56 @@
+import { isValidRequest } from '@sanity/webhook'
+import { sanityClient } from '../../lib/sanity.server'
+
+const AUTHOR_UPDATED_QUERY = `
+ *[_type == "author" && _id == $id] {
+ "slug": *[_type == "post" && references(^._id)].slug.current
+ }["slug"][]`
+const POST_UPDATED_QUERY = `*[_type == "post" && _id == $id].slug.current`
+
+const getQueryForType = (type) => {
+ switch (type) {
+ case 'author':
+ return AUTHOR_UPDATED_QUERY
+ case 'post':
+ return POST_UPDATED_QUERY
+ default:
+ throw new TypeError(`Unknown type: ${type}`)
+ }
+}
+
+const log = (msg, error) =>
+ console[error ? 'error' : 'log'](`[revalidate] ${msg}`)
+
+export default async function revalidate(req, res) {
+ if (!isValidRequest(req, process.env.SANITY_STUDIO_REVALIDATE_SECRET)) {
+ const invalidRequest = 'Invalid request'
+ log(invalidRequest, true)
+ return res.status(401).json({ message: invalidRequest })
+ }
+
+ const { _id: id, _type } = req.body
+ if (typeof id !== 'string' || !id) {
+ const invalidId = 'Invalid _id'
+ log(invalidId, true)
+ return res.status(400).json({ message: invalidId })
+ }
+
+ log(`Querying post slug for _id '${id}', type '${_type}' ..`)
+ const slug = await sanityClient.fetch(getQueryForType(_type), { id })
+ const slugs = (Array.isArray(slug) ? slug : [slug]).map(
+ (_slug) => `/posts/${_slug}`
+ )
+ const staleRoutes = ['/', ...slugs]
+
+ try {
+ await Promise.all(
+ staleRoutes.map((route) => res.unstable_revalidate(route))
+ )
+ const updatedRoutes = `Updated routes: ${staleRoutes.join(', ')}`
+ log(updatedRoutes)
+ return res.status(200).json({ message: updatedRoutes })
+ } catch (err) {
+ log(err.message, true)
+ return res.status(500).json({ message: err.message })
+ }
+}