lewisdale.dev/src/blog/posts/2022/10/building-a-cms-for-eleventy.md
Lewis Dale d332e34874
All checks were successful
Build and copy to prod / build-and-copy (push) Successful in 1m24s
make tag urls all lowercase, instead of having multiple urls for the same tag
2024-10-12 11:19:33 +01:00

70 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "Building a CMS for Eleventy"
date: 2022-10-07T00:00:00
slug: building-a-cms-for-eleventy
tags:
- eleventy
---
Three days ago, I tweeted this:
[https://twitter.com/LewisDaleUK/status/1577211142748807168](https://twitter.com/LewisDaleUK/status/1577211142748807168)).
I said I wouldnt be writing a CMS for Eleventy. It wasnt going to happen, theres no way. Im not in the business of reinventing the wheel.
Anyway, heres how I built a (very simple) CMS for an Eleventy site.
## [Why?](https://lewisdale.dev/post/building-a-cms-for-eleventy/#why)
I wanted to build a proof-of-concept for something Id had in mind a while ago, which was a little application that could build a static web page for a local café, and allow the owners to put together new menus and have them update without any intervention from a developer.
I knew it wasnt hard to use external data sources with Eleventy - this site uses one [to get book information for my reading list](https://lewisdale.dev/post/managing-my-reading-list/). What I wanted to do was seamlessly trigger that build and data retrieval.
Firstly I considered a different approach: committing files to a Git repository and pushing them. Thats fine in theory, but its very config-heavy, and relies on having an authenticated Github account attached, which isnt ideal. I want to be able to trigger the *actual* build.
## [How?](https://lewisdale.dev/post/building-a-cms-for-eleventy/#how)
At its core, this is just an Express server with an SQLite database and the [Eleventy programmatic API](https://www.11ty.dev/docs/programmatic/). I went with Express because it meant I could keep everything inside Javascript (well, Typescript), meaning I wouldnt have to execute commands from whatever platform Id written - simply, it makes it slightly easier from a package management perspective.
The flow is actually really simple. Once a user saves a menu, we trigger the Eleventy build in a separate directory. The directory contains a full Eleventy instance; this doesnt rely on the end-users configuration, as the API means I can inject what config I need and leave everything else untouched. This then builds it separately, and I can serve the files any way I want.
## [Issues encountered](https://lewisdale.dev/post/building-a-cms-for-eleventy/#issues-encountered)
The Eleventy Programmatic API isnt particularly well-documented, so I had to go digging through the code to work out what was going on in some spots. In particular, Id assumed that the paths I provided for output directories and config files were relative to the input path, but that proved to be false - theyre actually relative to the working directory. So while I thought I was looking for `.eleventy.js` in `/eleventy_dir/`, it was actually looking in the directory of the Express app.
This was also true for passthrough copies, which proved to be a slight issue - one of the things I didnt want to do was dictate how the Eleventy site should be configured. In the end, I found a “workaround” (read: horrible hack) that let me override the eleventyConfig.addPassthroughCopy function, and make relative paths absolute. Heres the code for it below:
```javascript
new Eleventy(
this._config.buildDir,
this._config.outputDir,
config: (eleventyConfig) => {
let addPassthrough = eleventyConfig.addPassthroughCopy.bind(eleventyConfig);
eleventyConfig.addPassthroughCopy = (file) => {
if (typeof file === "string") {
const filePath = {
[path.join(this._config.rootDir || "", file)]: file
}
return addPassthrough(filePath);
}
return addPassthrough(file);
}
eleventyConfig.addGlobalData("menus", () => {
return menus as CollectionItem[];
});
return {};
}
)
```
Like I said, a “workaround”.
## [Final thoughts](https://lewisdale.dev/post/building-a-cms-for-eleventy/#final-thoughts)
So this was a fun little experiment. Its very rough-and-ready and doesnt really do a lot, but it was good to spike out how that might be done. Eagle-eyed observers of the codebase well see that theres lots of boilerplate/half-finished code for other things I was working on. Im planning on adding more features to the server, and then hopefully building an MVP of the menu application.
I think there are a few use cases for this, but mostly its a good way to build content-managed websites that are updated relatively-infrequently. I think the thing that I like about it is that it is very unprescriptive. Your specific Eleventy configuration isnt important - it adds the data it needs, and then leaves it alone (well, everything except those file paths).
The source for the Express server can be found [on my Github](https://github.com/LewisDaleUK/11ty-building-example/).