On-Demand ISR for Ghost w/ NextJS
Here's a quick snippet that allows for On-Demand ISR (incremental static regeneration) in NextJS and Ghost CMS (headless). This gets triggered as a webhook anywhere in your api/ folder.
You'd also set a webhook secret in your environment variables to protect it from misuse. There's a couple of lines specific to this site, but the file should mostly remain the same after adjusting it to your use case.
It detects the revalidation content type – posts or pages – and revalidates a path with a given mapping.
// /pages/api/ghost/revalidate.ts
import { NextApiRequest, NextApiResponse } from "next";
import get from "lodash/fp/get";
import { REVALIDATE_KEY } from "../../../config";
import { postMessage } from "../../../lib/discord";
import isError from "lodash/isError";
import reduce from "lodash/reduce";
interface RevalidateMap {
[type: string]: (slug: string) => string;
}
const PATH_MAP: RevalidateMap = {
post: (slug) => `/posts/${slug}`,
page: (slug) => `/${slug}`,
};
interface SlugItem {
type: "post" | "page";
slug: string;
}
function collectSlugs(req: NextApiRequest): SlugItem[] {
return reduce(
Object.keys(req.body),
(result: SlugItem[], value) => {
const currentSlug = get(`${value}.current.slug`, req.body);
const previousSlug = get(`${value}.previous.slug`, req.body);
if (previousSlug === undefined && currentSlug) {
return [
...result,
{ type: value as SlugItem["type"], slug: currentSlug },
];
} else if (currentSlug === undefined && previousSlug) {
return [
...result,
{ type: value as SlugItem["type"], slug: previousSlug },
];
} else {
return [
...result,
{ type: value as SlugItem["type"], slug: currentSlug },
{ type: value as SlugItem["type"], slug: previousSlug },
];
}
},
[]
);
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Check for secret to confirm this is a valid request
if (req.query.secret !== REVALIDATE_KEY) {
return res.status(401).json({ message: "Invalid token" });
}
try {
const collected = collectSlugs(req);
// Revalidate post lists.
await res.revalidate("/");
await res.revalidate("/posts");
// Revalidate individual pages.
await Promise.all(
collected.map(async (item) => {
const path = PATH_MAP[item.type](item.slug);
await res.revalidate(path);
await postMessage(`✅ Revalidated w/ ISR: \`${path}\``);
})
);
return res.json({ revalidated: true });
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
// eslint-disable-next-line no-console
console.error(err);
let message = "Unknown Error";
if (isError(err)) message = err.message;
postMessage(`⚠️ Failed to on-demand revalidate. \`${message}\``);
return res.status(500).send("Error revalidating");
}
};After adding this endpoint to your NextJS site, you'd add the URL to Ghost webhooks on any related events (page/post updated, published, etc).
Your pages should now update incrementally without a full build process. This works great on Vercel!
