- 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.
Step 2: Data fields
Create any additional fields you want your blog posts to have. As an example, here's mine...
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.
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).
If you want, you can select "All Access", but I recommend clicking "Custom"
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...
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