Using Directus CMS with Astro SSR

Nov 20, 2023

astrodirectus
  • Why use a CMS system?
  • Why Directus and Astro
  • Setting up Directus on Fly
  • Adding post index to Astro
  • Adding a post page to Astro

Why use a CMS system?

This humble page of mine has undergone various rewrites during the years. Originally a plain HTML page, then PHP, then Drupal, then Gatsby and finally Astro.

What all of these changes had in common, was a painful migration process. I like to write in Markdown, but dealing with static files, and images can be quite annoying. Using something like Directus allows me to keep the content somewhere safe, while I maybe rewrite the site again.

Why Directus and Astro

This could've easily been any self-hostable CMS, but someone from the Danish Developer Community recommended it to me. What sold it, was humble requirements, SQLite support and a simple stack. I wanted something that I could throw at fly.io and let them handle it.

Installing Directus

You can get a 14 day trial on their cloud-hosted solution or you can self-host, like I did.

After this section, you should be logged in on your Directus instance with an admin login.

Setting up your CMS

Step 1: Datamodel

In the left side-bar go into "Settings" and "Data model". Create a new data type called "blog". Primary key should be a manual string called "slug". You may want some of the optional fields, I created my own.

creating data model

Step 2: Data fields

Create any additional fields you want your blog posts to have. As an example, here's mine...

data fields

Step 3: Creating a read-only role

Go into "Settings" and "Access Control". Click the plus-button in the top to create a new role. Give it read access to the "blog" collection. Otherwise you won't be able to read any posts.

read only role

Step 4: Create a user for Astro

In the sidebar, click "User Directory" and and the plus-button in the top right corner.

Create a user called "Blog". It's not supposed to log in, so don't give it a password.

In the bottom of the user-page in the "Admin" section, set its' role to "Read only" and generate a "static token". Write that down somewhere, you'll need it in a bit.

Step 5: Setting up file access

Directus has some default experience when it comes to files. Even in the preview part of the editor I'm writing this post in, it fails by default. But we can fix it!

Go into "Files" in the sidebar and create a new directory called "Blog".

Then go into "Settings" and "Access Control" and "Public". At the bottom of the list, is a hard-to-see link called "System Collections". Click that.

We want to give anonymous public users access to our blog assets, but not anything else. So, find directus_files and click the view setting (eye).

public file permissions

If you want, you can select "All Access", but I recommend clicking "Custom"

custom file permissions

Set "folder.name" to match "Blog". This allows anyone to read assets in the "Blog" directory.

Step 6: Set default asset directory

Go into "Settings", "Data model" and "Blog". Find the field you used for markdown (mine is "body"). In the field configuration under "Interface" you can set a "Root folder". Set that to "Blog". Now any assets uploaded there, will be put into the correct sub-directory.

Step 7: Example blog post

Go into "Content", "Blog" and create a new entry.

Making Directus available to Astro

This assumes you have an Astro site already. If you don't, try running npm create astro@latest and read the Getting started guide.

I'm using server-side-rendering (SSR), so your pages will look different if you're doing static-site-generation (SSG). Should still work just fine, but means that you need to make your build aware of your Directus token.

Setting up your environment

Now it's time to find that token you wrote down earlier.

DIRECTUS_URL=https://example.com
DIRECTUS_TOKEN=<YOUR_DIRECTUS_TOKEN>

Astro loads .env files by default. Be sure to add .env to your .gitignore file so you don't leak your credentials.

Install and start

npm install --save @directus/sdk markdown-it markdown-it-shiki
npm run dev

Talking to Directus

src/lib/cms/cms.ts

import { createDirectus, readItems, rest, staticToken } from "@directus/sdk";
import type { CollectionEntry } from "astro:content";
import type { DirectusBlog } from "./blog";

type Schema = {
	blog: Array<DirectusBlog>;
};

export function getDirectusClient() {
	const URL = import.meta.env.DIRECTUS_URL || process.env.DIRECTUS_URL;
	const TOKEN = import.meta.env.DIRECTUS_TOKEN || process.env.DIRECTUS_TOKEN;

	if (!URL || !TOKEN) {
		console.log("URL/TOKEN", URL, TOKEN);
		// Will fail at build time: https://fly.io/docs/reference/build-secrets/
		throw new Error("Missing CMS URL and/or TOKEN.");
	}
	const directus = createDirectus<Schema>(URL)
		.with(rest())
		.with(staticToken(TOKEN));
	return directus;
}

export function getAssetURL(id: string) {
	return `${URL}/assets/${id}`;
}

Listing posts

src/pages/post/index.astro

---
import { readItems } from "@directus/sdk";
import { getDirectusClient } from "../../lib/cms/cms";
const client = getDirectusClient();
const posts = (await client.request(readItems("blog")));
console.log("posts", posts);
---

<section>
   <h3>Testing Directus</h3>
   {
   	posts.map((post) => (
   		<h4>
   			<a href={`./${post.slug}/`}>{post.title}</a>
   		</h4>
   	))
   }
</section>

Should look something like this...

listing posts

Show a post

src/pages/post/[...slug.astro]

---
import { readItem } from "@directus/sdk";
import { getDirectusClient, getAssetURL } from "../../lib/cms/cms";
import MarkdownIt from "markdown-it";
import shiki from "markdown-it-shiki"
const parser = new MarkdownIt({
   html: true
})
parser.use(shiki) // syntax highlighting
const client = getDirectusClient();

const { slug } = Astro.params;
if (!slug) return;
const post = await client.request(readItem("blog", slug));
const body = parser.render(post.body)
---

<p>{post.title}</p>
<div set:html={body}></div>

It isn't pretty, but it works!

Extra

Self hosting with Fly (skip if you're using hosted)

If you want to use fly.io, like me then you can add the following Dockerfile into a directory

Dockerfile
FROM directus/directus:10.8.1

Running fly launch should detect the Dockerfile and setup a fly.toml file for you.

Note: Fly's default RAM size kept crashing during init, so make sure to scale that to 512M, at least