Automatically revalidate deep types
This plugin is meant to create webhooks, but specifically for cache invalidations or page revalidations of your frontend applications.
In this plugin the word head is the head of your application. Of course Strapi is headless, but the frontends (heads) should still be updated when you update the data in strapi. By relying on caching in the heads, it can take a lot of workload off of your strapi instance.
This plugin let's you set up different types of heads and define rules for each of them for when a page or some content needs to be revalidated.
Install the plugin
1npm i @oak-digital/strapi-plugin-revalidator
The configuration has multiple parts.
The first thing to notice is headTypes
. headTypes
are used to describe the different types of heads for the application.
One might be a nextjs frontend and another could be a mobile app cache or github pages.
headTypes
should be a record, where the keys are the names of the head types used.
example:
1{
2 headTypes: {
3 nextjs: {
4 // ...
5 },
6 githubPages: {
7 // ...
8 },
9 },
10}
headTypes
should define customizable fields which are used to configure your heads with specific fields. These fields can be used to prepare a url or headers for a revalidation request.
example:
1{
2 nextjs: {
3 fields: {
4 endpoint: {},
5 secret: {},
6 },
7 },
8}
prepareFn: (strapi, fields, entry) => any
each headTypes
should also define which content types makes them revalidate. For example you might have a page
content type that should revalidate when changed.
You may also have designated pages for quotes or testimonials that needs to be revalidated in some other way.
To revalidate, you first need to prepare urls or whatever you need in the prepareFn
. The prepareFn
should return a state to be used for the revalidateFn
.
By default the revalidateFn
will simply do a fetch based on the following fields from the returned state.
1{
2 url,
3 body,
4 params,
5 method,
6}
url
is the url to request.
body
is the data that should be sent in the body.
params
is an object of query parameters that will be added to the url
.
method
is the method that should be used to request, by default "POST"
is used.
example:
1{
2 nextjs: {
3 // ...
4 'api::page.page': {
5 prepareFn: async (strapi, fields, page) => {
6 // ...
7 return {
8 url
9 };
10 },
11 },
12 }
13}
You may want to do something else than just making a request in the revalidation function. This can be customized by changing the revalidateFn
.
example:
1{
2 nextjs: {
3 // ...
4 'api::page.page': {
5 prepareFn: async (strapi, fields, page) => {
6 // ...
7 return {
8 url
9 };
10 },
11 // state is what is returned from prepareFn
12 revalidateFn: async (state) => {
13 // ...
14 fetch(state.url);
15 }
16 },
17 }
18}
Different log levels are available (none
, info
and debug
)
none
: revalidator will never log anything
info
(default): Will log whenever something is revalidated and other useful info.
debug
: extra logs mostly used for debugging.
It can be configured in the following way
1{
2 config: {
3 logging: {
4 level: "info",
5 },
6 },
7}
1export default ({ env }) => ({
2 revalidator: {
3 enable: true,
4 config: {
5 headTypes: {
6 myFrontend: {
7 // fields that can be configured
8 fields: {
9 endpoint: {
10 type: "string",
11 },
12 secret: {
13 type: "string",
14 },
15 },
16
17 // Define the revalidation rules for each content type
18 contentTypes: {
19 'api::page.page': {
20 prepareFn: async (strapi, fields, page) => {
21 const { endpoint, secret } = fields;
22 try {
23 const finalParams = {
24 url: page.url,
25 secret,
26 };
27 const finalUrl = `${endpoint}?${qs.stringify(finalParams)}`;
28 return {
29 finalUrl,
30 };
31 } catch (e) {
32 console.error(e);
33 }
34 },
35 revalidateFn: async (state) => {
36 try {
37 const data = await fetch(state.finalUrl);
38 } catch (e) {
39 console.error(e);
40 }
41 },
42 revalidateOn: {
43 page: {
44 ifReferenced: true,
45 revalidationType: 'soft',
46 },
47
48 article: [
49 {
50 ifReferenced: true,
51 },
52 // {
53 // // This should probably be changed in later versions, so it doesn't run on ALL pages
54 // predicate(page, article) {
55 // return page.attributes.content.some((block) => block.__component === 'LatestArticles');
56 // },
57 // },
58 ],
59
60 quote: {
61 ifReferenced: true,
62 revalidationType: 'soft',
63 },
64 },
65 },
66 'api::quote.quote': {
67 prepareFn: async (strapi, fields, quote) => {
68 const { endpoint, secret } = fields;
69 const finalUrl = `${endpoint}/quotes/${quote.id}?${qs.stringify({ secret })}`
70 return {
71 finalUrl,
72 },
73 },
74 revalidateFn: async (state) => {
75 try {
76 await fetch(state.finalUrl)
77 } catch (e) {
78
79 }
80 },
81 },
82 },
83 },
84 },
85
86 // Hard coded heads - this may be good for monorepos
87 defaultHeads: {
88 myFrontend: [
89 {
90 name: "My primary frontend",
91 // myFrontend's fields
92 fields: {
93 endpoint: env("FRONTEND_ENDPOINT"),
94 secret: env("REVALIDATION_SECRET"),
95 },
96 },
97 ],
98 },
99 },
100 },
101});
It is simple to revalidate all entries of a content type when a navigation item is changed: add the following to your revalidateOn object for the content type you want to revalidate.
1{
2 // your other revalidateOn contentTypes...
3 "plugin::navigation.navigation-item": {
4 revalidationType: "soft"
5 }
6}
example of revalidating page
1{
2 // ...
3 "api::page.page": {
4 prepareFn: () => {},
5 revalidateOn: {
6 "plugin::navigation.navigation-item": {
7 revalidationType: "soft",
8 },
9 // ... your other rules
10 },
11 },
12 // ...
13}
To develop on this plugin first open a strapi project and navigate to src/plugins/
and clone this repo or your fork
cd src/plugins
git clone git@github.com:Oak-Digital/strapi-plugin-revalidator.git revalidator
cd revalidator
Install dependencies with the
npm install
Then to make sure the build is always up to date run
npm run develop
Add the following basic config to config/plugins.ts
1export default ({ env }) => ({
2 // ...
3 revalidator: {
4 enabled: true,
5 resolve: "./src/plugins/revalidator",
6 },
7 // ...
8});
In another terminal go to the root of your project and run strapi develop --watch-admin
. This can probably also be run like the following depending on your setup.
npm run develop -- --watch-admin
npm install @oak-digital/strapi-plugin-revalidator
Check out the available plugin resources that will help you to develop your plugin or provider and get it listed on the marketplace.