This post is part of a series on my process and progress building out this site from a plain portfolio page into something that can actually hold writing, projects, and whatever else I decide belongs here.

When I first put this site together, it was mostly just a single Bootstrap page. It had the normal portfolio things: an about section, a skills section, some links to articles, and a contact form. In other words, it worked, but only in the way a résumé works. It pointed at things. It did not really contain them.

That became annoying pretty quickly.

The problem was simple: I had articles, but the site itself did not really have a place for them. I could link out to Medium, or I could keep adding thumbnail cards to the homepage until the page became a junk drawer. Neither of those seemed like a great long-term plan.

So the new goal became:

Keep the site I already had, but give it a real article system.

Which is how I ended up with Eleventy.

What is Eleventy?

Eleventy is a static site generator, which sounds fancier than it is. Basically, I write a post in Markdown, Eleventy runs through a template, and out comes a normal HTML page.

That means I do not need a database. I do not need WordPress. I do not need to log into an admin panel and fight with a visual editor that has strong opinions about line spacing.

I can just write something like this:

---
layout: article.html
title: "Gods and Snakes Part 1"
subtitle: "NLP on ancient languages"
date: 2020-08-09
tags:
  - articles
  - Ancient Greek
  - NLP
---

Article goes here.

Then Eleventy turns it into a page.

The file starts as this:

articles/gods-and-snakes-part-1.md

And becomes this:

_site/articles/gods-and-snakes-part-1/index.html

This is both very simple and weirdly satisfying.

Keeping the old site

One thing I did not want to do was start over.

This is a recurring danger with projects like this. You begin with “I just need to add a blog,” and three hours later you are redesigning the entire site, changing fonts, inventing a design system, and wondering whether your portfolio should have a dark mode toggle.

So I tried to keep the scope under control.

The homepage stayed basically the homepage. The navbar stayed the navbar. The same colors, fonts, footer, JavaScript, images, and general Bootstrap structure all stayed in place.

The main thing I added was an article template:

_includes/article.html

That template is the shell for every article. It keeps the same site wrapper, but swaps the homepage sections for an article layout.

So every post gets the same structure:

Title
Subtitle
Author/date/read time
Article body
Topics
Back to Articles

This was the first point where the site started to feel less like a class project page and more like an actual small website.

The config file

Most of the magic lives in a file called:

.eleventy.js

This file tells Eleventy what to do.

First, it needs to copy over the normal site assets:

eleventyConfig.addPassthroughCopy("css");
eleventyConfig.addPassthroughCopy("js");
eleventyConfig.addPassthroughCopy("assets");

Without that, the generated site would have HTML, but no styling, scripts, or images. Which is technically a website, but not one you want to show anyone.

Then I added a date formatter:

eleventyConfig.addFilter("readableDate", function (date) {
  return new Date(date).toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric"
  });
});

This lets the template display dates in a human way:

August 9, 2020

instead of whatever raw date format was sitting in the file.

Collections, or: how not to manually update links forever

The next problem was the article index.

If I have three articles, I can manually write three links. If I have thirty articles, that becomes annoying. If I move a file or change a title, then I have to remember to update the index too. This is how websites become haunted.

Eleventy collections solve that.

The basic collection says: “Find all the Markdown files in the articles folder and sort them newest first.”

eleventyConfig.addCollection("articles", function (collectionApi) {
  return collectionApi
    .getFilteredByGlob("articles/*.md")
    .sort(function (a, b) {
      return b.date - a.date;
    });
});

Then the articles page can loop over them:


  <a href="/articles/i-just-wanted-a-better-funnel-report/">I Just Wanted a Better Funnel Report</a>

  <a href="/articles/data-work-is-translation-work/">Data Work Is Translation Work</a>

  <a href="/articles/building-this-site-eleventy/">Building This Site Part 1</a>

  <a href="/articles/building-this-site-part-2/">Building This Site Part 2</a>

  <a href="/articles/gods-and-snakes-part-4/">Gods and Snakes Part 4</a>

  <a href="/articles/gods-and-snakes-part-3/">Gods and Snakes Part 3</a>

  <a href="/articles/gods-and-snakes-part-2/">Gods and Snakes Part 2</a>

  <a href="/articles/gods-and-snakes-part-1/">Gods and Snakes Part 1</a>

Now adding a new article is just adding a new Markdown file.

That is the kind of laziness I respect.

Series were a little trickier

The first real set of posts I moved over was Gods and Snakes, a four-part series from my General Assembly capstone work.

A normal article list should be newest first. That makes sense.

A series should not be newest first. Nobody wants to read:

Part 4
Part 3
Part 1
Part 2

That is not mysterious. That is just irritating.

So each post in a series gets two extra fields:

series: "Gods and Snakes"
part: 1

The series field says what group it belongs to. The part field says where it belongs in that group.

That way, the overall articles page can still sort newest first, while the series section can sort by part number.

This was one of those tiny problems that feels silly until it breaks. Then suddenly it matters a lot.

Images caused the usual nonsense

Of course, the images did not work the first time.

This is partly because article pages live in a different folder than the homepage. An article might be at:

/articles/gods-and-snakes-part-1/

while an image might be at:

/assets/img/article/fullsize/article1.png

At one point I had something like this:

![Apollo killing Python](..assets\img\article\fullsize\article1.png)

Which is wrong in at least two ways:

  1. ..assets is missing a slash.
  2. Web paths use /, not Windows \.

The working version was:

![Apollo killing Python](/assets/img/article/fullsize/article1.png)

The leading slash means “start at the site root.”

This was one of those moments where the browser is not wrong, but you still feel like it is being difficult on purpose.

Making it look less terrible

Once the pages existed, they still needed to look right.

The first issue was the navbar. At the top of the page, the navbar text is white. That works over a dark hero image on the homepage. It does not work over a pale article background unless your goal is invisible navigation.

So the article pages and the articles index got a darker charcoal background at the top. The actual article content sits on a lighter card, which keeps it readable while still matching the rest of the site.

The second issue was images and captions.

Markdown makes images easy:

![Apollo killing Python](/assets/img/article/fullsize/article1.png)

*Apollo killing Python*

But by default, that does not necessarily look like an image and caption. It just looks like an image followed by italic text that may or may not belong to it.

So I added styling to center the images and make the following italic line look more obviously like a caption:

.article-body img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 2rem auto 0.75rem;
  border-radius: 0.5rem;
}

.article-body p:has(img) + p {
  text-align: center;
  color: #6c757d;
  font-size: 0.9rem;
  font-style: italic;
}

This is a small thing, but small things are most of what web design is.

The articles page

After individual articles worked, I needed a page to hold all of them.

That became:

articles/index.html

This page is basically the site’s little library catalog. It has a featured series section and a list of all articles.

The homepage now just needs a button:

<a class="btn btn-light btn-xl" href="articles/">View All Articles</a>

That is much cleaner than trying to make the homepage carry every piece of writing forever.

The homepage can stay a homepage. The articles page can do article things.

Building and publishing

The local workflow is:

npm run start

That starts the local preview server, usually at:

http://localhost:8080/

When it is time to publish, the command is:

npm run build

That generates the final static site into:

_site/

The important rule is:

Do not edit _site. Edit the source files, then rebuild.

The _site folder is the output. It is the cake, not the recipe.

So what did this actually do?

Before this, the site was a portfolio page with article links.

Now it has:

  • a homepage
  • individual article pages
  • a central articles index
  • automatic article listing
  • series support
  • Markdown-based writing
  • reusable templates
  • local preview
  • a static publishable build

That is not a massive system, but that is the point. It is enough structure to grow without becoming a chore.

I like that kind of project. It solves the problem in front of it, leaves room for future work, and does not require pretending that a personal website needs enterprise architecture.

Future posts in this series may get into the messier parts: styling choices, hosting under a subfolder, organizing project pages, or whatever breaks next.

Because something always breaks next.

ἔργον δ’ οὐδὲν ὄνειδος
“Work is no disgrace.”
— Hesiod