All checks were successful
Build and copy to prod / build-and-copy (push) Successful in 1m24s
70 lines
4.8 KiB
Markdown
70 lines
4.8 KiB
Markdown
---
|
||
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 wouldn’t be writing a CMS for Eleventy. It wasn’t going to happen, there’s no way. I’m not in the business of reinventing the wheel.
|
||
|
||
Anyway, here’s 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 I’d 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 wasn’t 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. That’s fine in theory, but it’s very config-heavy, and relies on having an authenticated Github account attached, which isn’t 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 wouldn’t have to execute commands from whatever platform I’d 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 doesn’t rely on the end-user’s 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 isn’t particularly well-documented, so I had to go digging through the code to work out what was going on in some spots. In particular, I’d assumed that the paths I provided for output directories and config files were relative to the input path, but that proved to be false - they’re 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 didn’t 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. Here’s 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. It’s very rough-and-ready and doesn’t really do a lot, but it was good to spike out how that might be done. Eagle-eyed observers of the codebase we’ll see that there’s lots of boilerplate/half-finished code for other things I was working on. I’m 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 it’s 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 isn’t 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/).
|