On-demand revalidation ⚡️
Tags: webdev, javascript, nextjs, strapi
Published: 2023-06-11 13:13:42

## Table of Contents
&nbsp;1. [Introduction:](#introduction)
&nbsp;2. [What is On-demand validation and how it works:](#what-is-ondemand-validation-and-how-it-works)
&nbsp;3. [Setup:](#setup)
&nbsp;4. [A Catch](#a-catch)
&nbsp;5. [Conclusion](#conclusion)
&nbsp;6. [References](#references)


## Introduction:

When a blog page or a product landing page is created, we prefer them to be static pages. Because, it is less likely to have a dynamic content or complex interactions on them. In an organisation, it is not scalable to have a developer involvement for publishing each marketing page or blog page. Hence, it is logical to off board the publishing process from developer to the respective teams which are managing the content. 

This leads to separation of content rendering and content publishing process. The ideal flow would be, when a team publishes a content(in a certain tool), the content should reflect in the desired target(website maybe) without any developer involvement. 

In this blog, we will discuss on how to achieve the above mentioned functionality using a headless CMS, Next JS and Vercel's infrastructure. 

## What is On-demand validation and how it works:

When a static website is deployed to Vercel, the page is actually served from Vercel's CDN, it is also called as 'edge network'. Edge network lies near to a consumer region to serve a swiftly. When a deployment happens the CDN's cache will be invalidated. Hence, when a request comes to a CDN, the page request will be served from Vercel's server and the new page will be stored in CDN's Cache. 

There can be an use-case where a page might have to refresh its content in a periodic time interval. To support this **Incremental static regeneration** is available.

However, there can also be certain use cases, where a page doesn't have to refresh its content in an interval, but, it should reflect a new/updated content whenever a new data is added, deleted or updated in a CMS. **On-demand validation** will help us solve this. 

When an operation is performed on a content in a CMS, we will make use of `revalidate` api from the NEXT JS, to invalidate a cache in the Edge CDN, which in-turns refresh a respective page. Basically, it is imitating a deployment for a particular page. 

## Setup:

We are going to use Strapi and Next JS(Page router) in this blog post to create a blog listing page.

Create a Next app with page router configuration 
`npx create-next-app@latest`
 
Create a strapi project `yarn create strapi-app my-blogs-strapi`

Before proceeding to our Next JS project, let's finish our Strapi part. 

In Strapi, we will create a content type which represents how a blog post looks like. Once, a content type is defined, an author can publish a content corresponding to a content type using content manager.

![Content Type and Content Manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgkzg4ng7i0uwiyispsd.png)

For the scope of this blog post, let's create a content type called `blog` which has `blogTitle` and `blogDescription` fields. 
![Content type - blog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11ive9qroh3pnne83ewh.png)

After creating a content type, we will create Webhook for CRUD(Create, Read, Update and Delete) operations. 

![Webhook creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qdioxt83ylj4u0ep6x0b.png)

To create a Webhook, an endpoint for making a post request by Strapi when there are any operation performed on a content type, needs to be created. We will use Next JS api route to create the endpoint.

![Webhook creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k22k0rz7gqbfnz4x2rwg.png)

Let's create a file named `revalidate.js` inside the api folder. The path for revalidation will be `/api/revalidate`. A POST request with a request body will be made to this endpoint by Strapi.

The request body associated from strapi has three main attributes, 

- entry
- model
- event

`event`: Describes whether a content is created, updated, published or unpublished. 

`model`: Represents a content type. In this case, it is `blog`.

`entry`: Represents the entry on which an action is performed from content manager.

Sample payloads can be referred from the codeblocks.

```
{
  event: 'entry.create',
  createdAt: '2023-06-11T11:42:51.225Z',
  model: 'blog',
  uid: 'api::blog.blog',
  entry: {
    id: 1,
    blogTitle: 'Blog 1',
    blogDescription: 'Blog description 1',
    createdAt: '2023-06-11T11:42:51.220Z',
    updatedAt: '2023-06-11T11:42:51.220Z',
    publishedAt: null
  }
}
```
```
{
  event: 'entry.update',
  createdAt: '2023-06-11T11:42:54.458Z',
  model: 'blog',
  uid: 'api::blog.blog',
  entry: {
    id: 1,
    blogTitle: 'Blog 1',
    blogDescription: 'Blog description 1',
    createdAt: '2023-06-11T11:42:51.220Z',
    updatedAt: '2023-06-11T11:42:54.456Z',
    publishedAt: '2023-06-11T11:42:54.455Z'
  }
}
```
```
{
  event: 'entry.publish',
  createdAt: '2023-06-11T11:42:54.458Z',
  model: 'blog',
  entry: {
    id: 1,
    blogTitle: 'Blog 1',
    blogDescription: 'Blog description 1',
    createdAt: '2023-06-11T11:42:51.220Z',
    updatedAt: '2023-06-11T11:42:54.456Z',
    publishedAt: '2023-06-11T11:42:54.455Z'
  }
}
```

However, we are interested in the attributes event and model. Because, event informs us what happened to a content type, and model describes what content-type got updated. Although, these attributes solves for the scope of this blog, if we have to revalidate a specific page, like blog details page, `entry` attribute will be useful. Without model, it might not be possible to know which page to be revalidated. For example, if a model is blog and a blog listing page is available, then, whenever an action is performed on the blog content type we have to revalidate the blog listing page.



Sample code for `revalidate.js`
```
const modelToPageMap = {
  blog: "/blogs",
};

export default async function handler(req, res) {
  const requestMethod = req.method;
  if (requestMethod === "POST") {
    const requestBody = req.body;
    const model = requestBody.model;
    await res.revalidate(modelToPageMap[model]);
    return res.json({ revalidated: true });
  } else {
    return res.status(405).json({
      text: "Method not supported",
    });
  }
}
```

## A Catch

We are passing a path to revalidate api. There are chances where a path doesn't exist. For example, when we create a new blog, the api will revalidate with /blog/[new-blog-url]. However, this path doesn't exist. Hence, revalidation will fail. In order to handle this scenario, a good practise is to pair getStaticPaths and getStaticProps. Instead of returning 404 for not found paths, if a fallback `true` is returned, a loader screen can be shown while the getStaticProps fetches a particular page's data. If the data is not available, we can redirect to 404 or handle in a customisable manner. Once the new page is constructed, revalidation can be performed when an update or un-publish action occur.


## Conclusion
On-demand revalidation helps us optimizing the number of revalidation performed when a page is Incremental Static Regenerated. Most of the matured CMS will have webhook to implement this functionality. For the purpose of this blog, the endpoint for webhook is given in localhost. For production-ready it needs to be replaced with an actual https endpoint. 

## References
- [Strapi Installation](https://docs.strapi.io/dev-docs/installation/cli)
- [On demand revalidation](https://nextjs.org/docs/pages/building-your-application/rendering/incremental-static-regeneration)