Juan Beldi

Juan Beldi

Posted on Oct 07, '24

On-Demand Revalidation with Next.js App Router and Directus Flow

Setting up the environment

Let's start by creating an .env file at the root of your Next.js app and add REVALIDATION_SECRET key with a secret value.

REVALIDATION_SECRET="[YOUR_SECRET]"

Setting up the re-validation API route in Next.js

Now let's create the API route that will handle our re-validation requests.

Create a new file at: app/api/revalidate_tag/route.ts, then place this script inside:

import { revalidateTag } from 'next/cache';
import type { NextRequest } from 'next/server';

enum Error {
InvalidSecret = 'invalid-secret',
InvalidTag = 'invalid-tag',
}

const errorMessages = {
[Error.InvalidSecret]: 'Invalid Secret',
[Error.InvalidTag]: 'Invalid Tag',
};

export async function POST(request: NextRequest) {
// data will contain the body sent in this request (we're going to send this via Directus Flow).
const data = await request.json();
// check that the secret provided matches the one in our app
const isSecretValid = data.secret === process.env.REVALIDATION_SECRET;
// check that we're receiving a tag
const isTagValid = Boolean(data.tag);

function getErrorMessage() {
if (!isSecretValid) return errorMessages[Error.InvalidSecret];
if (!isTagValid) return errorMessages[Error.InvalidTag];
}

const errorMessage = getErrorMessage();

// If there's an error, we don't re-validate
if (errorMessage) {
return Response.json({
revalidated: false,
now: Date.now(),
message: errorMessage,
});
}

// This is where the magic happens, when doing this, we're telling Next
// to purge any cache related to this tag
revalidateTag(data.tag);
return Response.json({ revalidated: true, now: Date.now() });
}

Setting up request tags in Next.js

Next, we'll set up the tags for our requests, this is how Next will know what requests to purge when using validateTags.

With fetch

If you're just doing vanilla fetch request you can simply do this by setting it up on the fetch's second parameter (RequestInit):

fetch('my-request', { next: { tags: ['my-tag'] } });

With custom fetch wrappers

In my case, I'm using Directus SDK (@directus/sdk) for requests, which means I need to wrap the request with Next's unstable_cache. I will be doing this in my blog article page at app/blog/[slug]/page.tsx.

I have a simple request to get a post from my posts collection via slug like this:

import { readItem } from '@directus/sdk';
import directus from '../client/directus';

export type PostData = {
slug: string;
title: string;
shortDescription?: string;
content: string;
};

export async function getPost(
slug: string,
version?: string
): Promise<PostData> {
return directus.request<PostData>(
readItem('posts', slug, {
version,
fields: [
'slug',
'title',
'shortDescription',
'content',
],
})
);
}

And in my page I use it like this:

type PageProps = {
params: {
slug: string;
};
};

export default async function PostPage({ params }: PageProps) {
const post = await getPost(params.slug);

...
}

So now I need to wrap the request in unstable_cache:

// Import unstable_cache
import { unstable_cache } from 'next/cache';

type PageProps = {
params: {
slug: string;
};
};

export default async function PostPage({ params }: PageProps) {
// Create unstable_cache proxy request with tag "posts" for this slug.
const getCachedPost = unstable_cache(getPost, [params.slug], {
tags: ['posts'],
});
const post = await getCachedPost(params.slug);

...
}

This is pretty much it on the Next.js side of things, let's move on with the Directus setup!

Setting up Directus Flow

In your Directus frontend, head to Settings -> Flows -> Create Flow (top right).

Directus setup step 1



In Flow Setup, choose a descriptive name for your flow. I'll be naming mine "Revalidate Posts"

Directus setup step 2



Now in Trigger Setup:

  • Choose Event Hook.
  • Set Type to Action (non-blocking).
  • In Scope, set when you want to trigger the flow. In my case I will be doing it on items.create, items.update and items.delete
  • In Collections, select the collection/s that should be targeted. In my case this will be Posts.
Directus setup step 3



Now press save and we should be inside the Flow dashboard. We're now at the final step of this guide (finally).

Let's create a new operation by clicking the + symbol to the right of our newly created trigger. In the operation modal, choose a descriptive name for your operation. I will be using "Revalidation request".

  • Select Webhook / Request URL.
  • For Method, choose POST
  • For URL, you'll want this to point to your Next.js API url, for example "http://127.0.0.1:3000/api/revalidate_tag"
  • In Request Body add (remember you can change tag to whatever tag you have setup).
{
"secret": "[YOUR_REVALIDATION_SECRET]"
"tag": "posts"
}
Directus setup step 4



That's it! Let's now test things out to see if this is working as expected.

Testing the Setup

Since the dev server has no cache, we need to create a build and run the build server:

npm run build && npm run start

Now go to your Directus frontend and create/delete/update a new post (or whatever item for the collection you're targeting), refresh your Next.js page and your changes should be visible almost instantly!