feb 21, 2023
🔗 How to build a minimalist Markdown blog with Eleventy
Anybody who ever started a new blog knows the feeling of pursuing a long list of features, only to get overwhelmed by the growing complexity. Knowing this, I tried to build this blog as a stripped-down container for my projects.
# A simple container
Let's admit it, UIs are cool, but usually the focus should be only on displaying content. Maybe fueled by some laziness, here are the choices made here:
- dark mode is automatic depending on the user's system preferences, no fancy buttons to switch between modes
- no reading metrics of any kind, no reading progress indicator, no time to read, no read counter statistics
- no comment system and social media buttons. Just no. Of course feel free to comment with an email
- no search or tag filtering, since there needs to be enough content to justify it
- no cookies Nobody likes to see a cookie popup as the first thing they see when visiting a website.
- no about page, when a paragraph can do.
A really simple about paragraph
# Markdown is all you need
Since the goal of this website is to document ongoing projects, it was important to have a setup that can store in a self-contained way all the content, text and images, in simple and portable formats.
Images and folders
To achieve this the website directory structure uses a separate folder for each post. In the same folder there is going to be the text stored in a markdown file and all the images referenced in the file content.
blog
├── post-title
│ ├── post-title.md
│ └── image.png
└── another-post
├── another-post.md
└── image.png
To ensure portability and compatibility with any future setup, it is important to have a pure Markdown syntax. In this setup we ditch the images shortcodes, that in the Eleventy starter template reference images with Nunjucks syntax, like this:
{% image "./possum.png", "Three possum hanging from a red balloon" %}
While to use the markdown syntax, images need to be referenced like this:
![Three possum hanging from a red balloon](./possum.png)
To achieve this, it would be possible to just set in eleventy.config.js
the option to output a copy of any images using the same folder structure, along those lines:
eleventyConfig.addPassthroughCopy(
"content/blog/**/*.{svg,webp,png,jpeg,jpg,avif}"
);
But that would mean no image optimization, no responsiveness at all, and that's not great, luckily this cool markdown-it
plugin exists, solution-loisir/markdown-it-eleventy-img that allows to parse the images using the markdown syntax. Mission accomplished.
Nunjucks templates
The 11ty/eleventy-base-blog is a great starting point to to build a blog, but with some changes to the repository structure it's possible to make it future-proof.
For example, it can be confusing that Nunjucks files can be used as content for templates as they support FrontMatter syntax. But it might be preferable to have a clear separation where Nunjucks files work only as templates and Markdown files for content. To do this let's convert all Nunjucks files in the /content
folder to .md
. Also all nunjucks syntax is preprocessed in markdown by default, but it can be turned off in eleventy.config.js
:
markdownTemplateEngine: false;
Also the nunjucks templates in /layouts
were using frontmatter to set the layout.
---
layout: layouts/base.njk
---
But it's possible to convert them to pure Nunjucks syntax by stating which layout they are extending like this:
{% extends "layouts/base.njk" %}
{% block content %}
...
{% endblock %}
Linking markdown files
It's possible to link to other internal posts using markdown syntax, but this usually requires to use the FrontMatter permalink
property, but this too can be too setup-specific. Moreover today VSCode allow you to drag-and-drop the file into the editor, which automatically generates the relative link.
But this, just like with image assets, won't work when the content is rendered in HTML because the folder structure will change at build-time and also the linked markdown file will be converted to index.html
.
So this won't work:
[Post title](../post-title/post-title.md)
But this one below will, because index.html
is the default file for a folder, so we just need to omit everything that comes after the last slash from the link.
[Post title](../post-title/)
To edit the links at build-time there is another markdown-it plugin, martinheidegger/markdown-it-replace-link, which allow to set some rules inside eleventy.config.js
to replace the relative links with the correct ones.
mdLib.use(markdownItLinks, {
processHTML: true, // defaults to false for backwards compatibility
replaceLink: function (link, env, token, htmlToken) {
if (link.includes(".md")) {
// remove the content after the last slash
const newLink = link.replace(/[^/]*$/, "");
return newLink;
}
return link;
},
});
Typography
Even a minimalist blog can still have great typography, and since most of the content would be text and code anyways, it's very easy to have nice looking website with a few steps:
-
Extracting the
.prose
class styles from the tailwindlabs/tailwindcss-typography plugin and adding it to the markdown CSS files, provides a great starting point to tweak going forward. -
Install the 11ty/eleventy-plugin-syntaxhighlight and pick a Prism theme to add it to the CSS files. This blog is using the
prism-one-dark
theme.
Layout
It's an interesting challenge to play with the constraints of Markdown syntax and a minimal setup, to try enriching the typography of what's available with the language. Thanks to modern CSS we can improve the expressiveness of the layout by adding some rules.
- All the post content is just rendered markdown. I'm manually writing the post date and post title for each post. The CSS targets the first
<p>
node at the top of each post, before the main<h1>
title to style the date. The CSS is also targeting the first italic node after the<h1>
title with the sibling selectorh1 + p:has(em)
to render an introductory text, when the article has one.
article > p:first-child {
color: var(--md-alt-txt);
font-size: 1rem;
}
h1 + p:has(em) em {
font-size: 1.4rem;
font-weight: 400;
}
-
If I have an italic text right after an image, I want to style it to make it look like a actual caption. I'm using the sibling selector
img + em
to target the first italic text in my CSS. -
When I put two adjacent images and their captions in my markdown it will create a double column layout with the images side-by-side. CSS lets us achieve this pretty easily with a combination of the parent selector
:has()
, sibling selectors and CSS grid.
@media (min-width: 768px) {
p:has(picture ~ picture),
p:has(img ~ img) {
display: grid;
grid-auto-flow: column;
grid-gap: 0 1.5em;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: repeat(2, auto);
}
p:has(picture ~ picture) img,
p:has(img ~ img) img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
Two pictures of beautiful possums that will be side by side on desktop
- I add links and resources at the bottom of the post manually, while I there are plugins that do this, it's nice to have the simplest setup possible. Again with CSS it's possible to target the resources list and style them differently from the article's lists. I also use a
::after
pseudo-element and itscontent
property to have the URL of the link displayed inline, which helps to identify links at a glance.
h3#resources ~ ul {
list-style-type: disc !important;
}
h3#resources ~ ul > li > a::after {
content: "(" attr(href) ")" !important;
}
- Whenever I want to add a little outgoing arrow icon next to a link, usually because it's the homepage of an external resource, I make sure that its URL doesn't end with a forward slash and let the CSS style it.
a:not(a[href$="/"])::after {
content: "↗︎";
display: inline-block;
vertical-align: text-top;
font-size: 0.7rem;
margin-right: 0.2em;
margin-left: 0.2em;
width: 1.1em;
height: 1.1em;
color: currentColor;
}
# Resources
- Plausible Analytics
- 11ty/eleventy-base-blog
- solution-loisir/markdown-it-eleventy-img
- W3C - Cool URIs don't change
- Markdown and Visual Studio Code
- martinheidegger/markdown-it-replace-link
- tailwindlabs/tailwindcss-typography
- 11ty/eleventy-plugin-syntaxhighlight
- PrismJS
- Working With Web Feeds: It’s More Than RSS
- Automatically generate open graph images in Eleventy