Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
instantly publish DatoCMS pages with Next 12.1 On-Demand ISR
// pages/api/webhooks/datocms/revalidate-pages.function.ts
// custom page extensions in next.config.js: pageExtensions: ['page.tsx', 'function.ts', 'page.jsx', 'function.js'],
import type { NextApiRequest, NextApiResponse } from 'next';
import { i18n } from 'next.config';
import type { Payload } from './types';
import {
getModelId,
getWebhookHeaders,
revalidateMultiplePaths,
} from './utils';
const { defaultLocale } = i18n ?? {};
const lookupModelApiKey = {
'1654963': 'composable_page',
'1654955': 'home_page',
'1654957': 'journal_article',
'1654956': 'journal_index',
} as const;
const modelApiKeys = Object.values(lookupModelApiKey);
type ModelApiKey = typeof modelApiKeys[0];
const getModelApiKey = (payload: Payload): ModelApiKey | undefined => {
return lookupModelApiKey[getModelId(payload)];
};
const getSlugsWithLocale = (
payload: Payload
): [locale: string, slug: string][] => {
return Object.entries<string>(payload?.entity?.attributes?.slug ?? {});
};
const getRevalidatePaths = (payload: Payload) => {
const modelApiKey = getModelApiKey(payload);
return getSlugsWithLocale(payload).flatMap(([locale, slug]) => {
const localePrefixed = locale !== defaultLocale ? `/${locale}` : '';
if (modelApiKey === 'home_page') {
return [localePrefixed || '/'];
}
if (modelApiKey === 'journal_index') {
return [`${localePrefixed}/journal`];
}
if (modelApiKey === 'composable_page') {
return [`${localePrefixed}/${slug}`];
}
if (modelApiKey === 'journal_article') {
return [`${localePrefixed}/journal/${slug}`, `${localePrefixed}/journal`];
}
return [];
});
};
const revalidatePages = async (req: NextApiRequest, res: NextApiResponse) => {
const revalidatePaths = getRevalidatePaths(req.body);
try {
const allRevalidated = await revalidateMultiplePaths(
res,
revalidatePaths
).then((results) =>
results.every((result) => result.status === 'fulfilled')
);
res.json({ allRevalidated });
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
res.status(500).send('Error revalidating');
} finally {
res.end();
}
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { secret } = getWebhookHeaders(req);
// Check for secret to confirm this is a valid request
if (secret !== process.env.PREVIEW_SECRET) {
return res.status(401).json({ message: 'Invalid token' });
}
return revalidatePages(req, res);
}
// pages/api/webhooks/datocms/types.ts
export type Entity = {
id: string;
type: string;
meta: Record<string, any>;
attributes: Record<string, any>;
relationships: Record<string, any>;
};
export type Payload = {
environment: string;
event_type: string;
entity_type: string;
entity: Entity;
};
// pages/api/webhooks/datocms/utils.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import type { Payload } from './types';
export const getWebhookHeaders = (req: NextApiRequest) => {
return {
siteId: req.headers['x-site-id']?.toString() ?? '',
webhookId: req.headers['x-webhook-id']?.toString() ?? '',
environment: req.headers['x-environment']?.toString() ?? '',
// this has to be added to the webhook config in DatoCMS dashboard
secret: req.headers['x-webhook-secret']?.toString() ?? '',
};
};
export const getModelId = (payload: Payload): string => {
return payload?.entity?.relationships?.item_type?.data?.id || '';
};
export const revalidateMultiplePaths = async (
res: NextApiResponse,
paths: string[]
) => {
const revalidate = res.unstable_revalidate.bind(res);
return Promise.allSettled(paths.map(revalidate));
};
@nibtime
Copy link
Author

nibtime commented Apr 20, 2022

Webhook Setup in DatoCMS

2022-04-20 20_40_09
2022-04-20 20_38_03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment