Skip to main content

auld lang syne: The Commonplace (micro)Log

So, I have to first admit how bizarre this new year day is for me. I wanted to fight the Hogmanay! Never before have I begun a year with my resolutions already successfully underway. To start, I've completed an entire year of writing 750 words daily each day.

And then, starting around November, I began publishing a full-length article nearly each day as well. I also restarted my poetry blog and began posting a new poem daily.

In regards to coding, I updated a bunch of my previous projects and started a bunch of new ones. I founded a indie web dev. studio with my amazing partner Yvonne called 🍓 Berry House and finally created the IndieWeb personal site of my dreams, brennan.day.

All of this is to say, well, where do I go from here?

The answer will probably make you roll your eyes, but it's another coding/writing project.

One of the reasons writing is so important to me is because having a daily written record means that I don't forget things. It means that days have proof of existence and aren't entirely wasted. I archive and collect and curate my life for the sake of examination. I believe meaningfulness can only be derived from examination.

Since I've transitioned from daily private journal writing to daily public blog posts, I've put less focus on my consumption. On one hand, this is a good thing. I think we all consume too much, and I love how I've been in such a creative, creating mode.

But on the other hand, you can't really make good work without appreciating the good work of others and standing on the shoulders of giants. I do not want to stop reading books or watching films or listening to music. Rather, I want to make sure I am far more intentional and deliberate about my media choices.

I also want to, well, remember what I consume. I enjoy using Letterboxd and Storygraph to keep track of my film-watching and book-reading respectively, but there is a lot of friction and piecemeal in that approach.

More importantly, my habit of keeping a gratitude journal has fallen to the wayside. I believe this to be one of the most important habits a person can have. I have so much to be grateful for.

So, with all of this in mind, I've decided to create a stand-alone project I'm calling my commonplace log and gratitude journal. It will live at https://log.brennan.day and will mostly just be for my own sake.

One of the biggest reasons I decided to make another site/project is because I'm a Beeminder zealot. I absolutely love the service and I make use of it a lot. (I actually have a tattoo of their logo, but that's a story for another blog post).

Beeminder has syncing service called GitMinder, which allows you to track the commits pushed to a specific repository on GitHub (and if you don't, you pay cold hard cash). Back in the day, you could keep track of all the commits you push, but GitHub changed their API so now it's repo-only. Which is actually great! This means that now I can hold myself accountable to ensuring I write out what I'm grateful for every day, and it will remove the friction of jotting down any media I've consumed or specific things I want to make note of.

So, this was my original napkin spec sheet of the site:

  • An ultra simple brutalist 11ty blog called "daily gratitude", at the repo commonplace.
  • Extremely simple, no CSS framework, monospace web fonts, etc.
  • Extremely accessible as well, good-looking on desktop and mobile without any responsive design
  • Homepage will render the date and note of gratitude of that day, it will be more like microblogging, so no titles, tags, images, etc.
  • Each month of posts grouped.
  • In addition to gratitude, I think it would be fun to also use this daily microblogging to track what I consume (books, videos, music), locations I go, quotes, anything else like that like a commonplace book.
  • Each type of data /media tracked has its own emoji/colour, and they're all optional.

Now, with all that out of the way, let's get into the technicalities of the site. Of course you might be thinking that a project this simple wouldn't need a technical write-up, I did too when I first started writing out the spec sheet. But oh my God, I was totally wrong.

The Deceptively Simple Spec Sheet

I began with Eleventy because I've been using it more and more, and it seemed like the perfect tool for this. Static site generator, supports Nunjucks templates, excellent for blogs. What could go wrong?

Well, I found myself going down a rabbit hole of timezone bugs, Nunjucks template escaping issues, and collection sorting headscratchers.

I scaffolded out a basic structure:

src/
  _includes/
    base.njk
    post.njk
  posts/
    2025-01-01.md
    2025-01-02.md
  index.njk
  css/
    style.css

Each post would be a simple markdown file with YAML frontmatter containing all the different types of data I wanted to track:

---
date: 2025-01-02
gratitude: "The silence of early morning before anyone else is awake is precious."

books:
  - title: "The Dispossessed"
    author: "Ursula K. Le Guin"
    status: "reading"
    pages: 102

videos:
  - title: "Static Site Generators Explained"
    creator: "Fireship"
    url: "https://youtube.com/watch?v=example"
    duration: "12m"

music:
  - title: "Nightswimming"
    artist: "R.E.M."
    album: "Automatic for the People"
    plays: 3
---

Clean data structure, easy to write, easy to track in Git. The Beeminder integration would work by me committing a new markdown file each day.

HTML Escaping

The first issue hit me when I tried to render the posts on the homepage. Everything was showing up as plain text. Not styled text, but literally the raw HTML tags were visible on the page. Angle brackets everywhere. It looked like I was teaching an HTML 101 class.

The culprit was Nunjucks' default behaviour of escaping HTML for security. Which is great when you're dealing with user input, but not so great when you're trying to render your own templates. The fix was adding the | safe filter to the content in the base layout:


<main id="main">
  {{ content | safe }}
</main>

Static site generators are secure by default, which means you need to explicitly tell them when you trust your own content.

Timezone Shenanigans

I'd create a post dated 2025-01-02, but it would show up on the homepage as January 1st. Or worse, it would show up in the wrong month entirely in the archives. December posts were appearing in November.

The issue? JavaScript's Date object was interpreting my YAML dates as local time and then converting them based on my timezone. Since I'm in a negative UTC timezone, dates were being shifted backward. A post from January 2nd at midnight was becoming January 1st at 1 PM the previous day. This is a known pitfall in the 11ty docs. RTFM.

The solution required two fixes:

First, parse all dates as UTC by appending the timezone:

const date = new Date(dateObj + 'T00:00:00Z');

Second, use string comparison instead of date math for sorting:

.sort((a, b) => {
  return String(b.data.date).localeCompare(String(a.data.date));
})

Even with these fixes, I hit another timezone snag. When grouping posts by month, January 1st was showing up in December 2024! The issue was that date.getFullYear() and date.getMonth() were still converting to local time. The final solution was to use UTC methods explicitly:

const utcYear = date.getUTCFullYear();
const utcMonth = date.getUTCMonth();
const monthKey = `${utcYear}-${String(utcMonth + 1).padStart(2, '0')}`;

Moral of the story: when working with dates in JavaScript, use UTC everywhere or prepare for timezone pain.

Collections

I wanted the homepage to show the latest post in full, and then monthly archives for everything else. Sounds simple. Eleventy has a great collections system. I wrote a custom collection:

eleventyConfig.addCollection("latest", function(collectionApi) {
  return collectionApi
    .getFilteredByGlob("src/posts/**/*.md")
    .sort((a, b) => String(b.data.date).localeCompare(String(a.data.date)))
    .slice(0, 1);
});

But my collections.all was returning 3 items when I only had 2 posts. Turns out, Eleventy was including the index.njk file itself as a collection item. The fix was to be more specific with glob patterns and filter more carefully.

I also needed to group posts by month, which meant creating another custom collection:

eleventyConfig.addCollection("byMonth", function(collectionApi) {
  const posts = collectionApi.getFilteredByGlob("src/posts/**/*.md");
  const byMonth = {};
  
  posts.forEach(post => {
    let date;
    if (typeof post.data.date === 'string') {
      date = new Date(post.data.date + 'T00:00:00Z');
    } else {
      date = post.data.date;
    }
    const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
    
    if (!byMonth[monthKey]) {
      byMonth[monthKey] = {
        year: date.getFullYear(),
        month: date.toLocaleString('en-US', { month: 'long', timeZone: 'UTC' }),
        monthKey: monthKey,
        posts: []
      };
    }
    
    byMonth[monthKey].posts.push(post);
  });
  
  return Object.values(byMonth).sort((a, b) => {
    return b.monthKey.localeCompare(a.monthKey);
  });
});

The archive logic had its own quirks. I initially excluded the entire month of the latest post from the archives, which meant January 1st disappeared when January 2nd was the latest post. The fix was to exclude only the single latest post, not the whole month:


{% for post in month.posts %}
  {% if not (collections.latest and post.data.date == collections.latest[0].data.date) %}
    <!-- render post -->
  {% endif %}
{% endfor %}

This felt like overkill for a "simple" blog, but it works.

Blank Individual Posts

When I clicked on an archive link, I'd get a 404. Or when the routing worked, I'd get an empty page. The posts weren't using any layout template.

I had to create a dedicated post layout _includes/post.njk and then add layout: ../_includes/post.njk to each post's frontmatter. But the layout needed to access the frontmatter data differently than I expected.

In the homepage template, I could access a post as post.data.gratitude. But in the individual post layout, the data is available directly as gratitude. This inconsistency was confusing until I understood Eleventy's data cascade—templates have different contexts depending on whether they're rendering a collection item or the page itself.

Brutalism Meets Gruvbox

For the design, similar to brennan.day I went full brutalist. No CSS framework. No preprocessors. Just vanilla CSS with CSS custom properties for the Gruvbox Dark colour scheme:

:root {
  --bg: #282828;
  --fg: #ebdbb2;
  --bg0-h: #1d2021;
  --gray: #928374;
  --red: #fb4934;
  --green: #b8bb26;
  --yellow: #fabd2f;
  --blue: #83a598;
  --purple: #d3869b;
  --aqua: #8ec07c;
  --orange: #fe8019;
}

Each content type gets its own color-coded left border:

  • 🙏 Gratitude: Yellow
  • 📚 Books: Blue
  • 📺 Videos: Purple
  • 🎵 Music: Green
  • 📍 Locations: Red
  • 💬 Quotes: Aqua
  • 📝 Notes: Orange

The CSS is under 200 lines total. Monospace font stack ('Courier New', Courier, monospace). Fixed width container at 600px. No media queries needed as it just works on mobile because of the simplicity.

The Finishing Touches

Once the core was working, I added the polish: RSS feed, sitemap, robots.txt, custom 404 page, social meta tags, favicons, and a footer with proper attribution. I even made the header clickable back home with a subtle yellow hover effect. Cache busting on the CSS means no more hard refreshes for visitors to see style updates.

The Human Element Redux

The debugging and technical wrangling why I love building things.

Yes, the timezone bugs were frustrating. Yes, the template escaping issues made me question my sanity. But solving these problems reminded me that even "simple" projects teach you something. Every bug is a chance to understand your tools better.

This gratitude log will now be my daily practice. Each morning, I'll commit a new markdown file. Beeminder will keep me accountable. The site will archive my consumption and reflections. And because I built it myself, from scratch, with all the attendant struggles and small victories, it feels truly mine.

The code is open source at github.com/brennanbrown/commonplace if you want to fork it or learn from my mistakes. The live site is at log.brennan.day.

Here's what I'm grateful for today: the reminder that simple things are rarely simple, and that's okay. The complexity is where the learning lives.

I hope this was an interesting read, and not your usual yearly retrospective.


What are you grateful for today? And more importantly, how are you keeping track of it?

White rabbit, white rabbit, white rabbit! —Celtic folklore


Webmentions

No webmentions yet. Be the first to send one!


Related Posts

↑ TOP