Back to Next.js

Roger Stringer

Roger Stringer / January 22, 2023

3 min read

This blog was on Remix for the past six months or so, and I still have other sites on Remix, and continue to build with Remix as it's a great framework, but I decided to move this one back to Next, mostly so I could play with some recent additions in Next with Next 13.

I'm not using Next's new app folder yet since that's still in beta, but I will play around with that feature as it becomes more stable.

On the frontend, it's using MDX to generate it's markup you see on posts, with tailwind handling the styling.

In the backend, I'm using Directus as a headless CMS which hasn't changed, then I'm using Flows to handle things like implementing Incremental Static Regeneration by letting Vercel know a post has been updated as a button in the Directus sidebar.

I've also added another flow that can be used to trigger a full site rebuild if needed, although the ISR flow handles most things quite nicely since I pass the collection and the key(s) being changed and the site then updates the appropriate pages.

And now here's some code to show how to set up ISR with flows.

Create a flow

You could set this flow to trigger on creation or update of certain collections but in this case, I prefer a manual trigger flow that I can just hit a button and have it update on the frontend.

Choose a webhook action:

Next, in the flow, set the content-type, a webhook secret and pass the body to the webhook:

Then, create pages/api/revalidate.ts:

pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getDirectusClient } from '~/lib/directus-server';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {  
  const body = await readBody(req);
  
  const headers = req.headers;
  if (!headers["x-webhook-secret"]) {
    return res.status(403).send("Forbidden");
  }

  const receivedSecret = headers["x-webhook-secret"];

  const secret = process.env.REVALIDATE_SECRET;

  if (receivedSecret !== secret) {
    return res.status(403).send("Forbidden");
  }
  
  const directus = await getDirectusClient();

  const { collection, keys } = JSON.parse(body);
	
  if (collection === "posts") {
    for (const key of keys) {
      const directusRes = await directus.items(collection).readOne(key, { fields: ["slug"] });
      await res.revalidate(`/blog/${directusRes.slug}`); // post
    }      
    await res.revalidate('/blog'); // blog page
    await res.revalidate('/'); // homepage
  } else if (collection === "snippets") {
    for (const key of keys) {
      const directusRes = await directus.items(collection).readOne(key, { fields: ["slug"] });
      await res.revalidate(`/snippets/${directusRes.slug}`); // snippet
    }      
    await res.revalidate('/snippets'); // snippets page

  } else if (collection === "page") {
    for (const key of keys) {
      const directusRes = await directus.items(collection).readOne(key, { fields: ["slug"] });
      await res.revalidate(`/${directusRes.slug}`); // page
    }
  }
    return res.status(200).send("Success");
}

export const config = {
  api: {
    bodyParser: false
  }
}

async function readBody(readable: NextApiRequest) {
  const chunks = []
  for await (const chunk of readable) {
    chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)
  }
  return Buffer.concat(chunks).toString('utf8')
}

When this URL is called and includes an appropriate webhook secret, collection and key(s) it will then tell vercel which pages to re-generate.

Do you like my content?

Sponsor Me On Github