diff --git a/src/posts/2023/drying-up-now-page-templates-eleventy.md b/src/posts/2023/drying-up-now-page-templates-eleventy.md
new file mode 100644
index 00000000..2008ce1e
--- /dev/null
+++ b/src/posts/2023/drying-up-now-page-templates-eleventy.md
@@ -0,0 +1,147 @@
+---
+date: '2023-09-03'
+title: Drying up now page templates and normalizing data in Eleventy
+draft: false
+tags:
+ - Eleventy
+ - development
+---
+My now page consists of a number of similar sections — some bespoke text, a number of media grids and lists. The text is repeated once, the lists are easily templated out in [Liquid.js](https://liquidjs.com/) and the media grids are all quite similar. Given the prominence of the media grids on the page and the number of them I decided to consolidate them into a single template while also normalizing the data passed into them as best I could.
+
+If you want to skip all the reading, the diff for this [is here](https://github.com/cdransf/coryd.dev/commit/6dda493d7b6c0435bac8ee2a55179e9e1afb7acd). There were a few steps to take to get this done. First I went ahead and took an existing media template and went to work reconciling the slight differences between the artist, album, book, tv and movie displays as follows:
+
+{% raw %}
+
+```liquid
+{% if data.size > 0 %}
+ {% assign media = data | normalizeMedia %}
+
+ {% tablericon icon title %}
+
{{ title }}
+
+
+{% endif %}
+```
+
+{% endraw %}
+
+We pass in the data to populate the section as `data` which is passed to the `normalizeMedia` filter and assigned to a `media` variable. The `normalizeMedia` filter does a few things:
+
+```javascript
+module.exports = {
+ normalizeMedia: (media) =>
+ media.map((item) => {
+ let normalized = {
+ image: item['image'],
+ url: item['url'],
+ }
+ if (item.type === 'album') {
+ normalized['title'] = item['title']
+ normalized['alt'] = `${item['title']} by ${item['artist']}`
+ normalized['subtext'] = item['artist']
+ }
+ if (item.type === 'artist') {
+ normalized['title'] = item['title']
+ normalized['alt'] = `${item['plays']} plays of ${item['title']}`
+ normalized['subtext'] = `${item['plays']} plays`
+ }
+ if (item.type === 'book') normalized['alt'] = item['title']
+ if (item.type === 'movie') {
+ normalized['title'] = item['title']
+ normalized['alt'] = `${item['title']} - ${item['summary']}`
+ }
+ if (item.type === 'tv') {
+ normalized['title'] = item['title']
+ normalized['alt'] = `${item['title']} from ${item['name']}`
+ normalized['subtext'] = `${item.name} • ${item.episode}`
+ }
+ return normalized
+ }),
+}
+```
+
+Most notably the filter populates an object with the properties known to be common to each media type. Next, it iterates through the `media` object, checks the `type` property and mutates the `normalized` object to include properties specific to the given media type. When returned, our normalized media object can then be iterated through to populate a given media grid. For example, the `tv` media type maps `'title'` to `'title'`, `${item['title']} from ${item['name']}` to `'alt'` and so forth. This allows each different media type to be rendered in a grid that supports an object shape like this:
+
+```typescript
+{
+ image: string,
+ url: string,
+ title: string,
+ alt: string,
+ subtext?: string
+}[]
+```
+
+This normalization is made easier by updating the shape of the objects returned by my site's `_data` files to better align with the object shape required by the `normalizeMedia` filter. For example (from `artists.js`):
+
+```javascript
+return {
+ title: artist['name'],
+ plays: artist['playcount'],
+ rank: artist['@attr']['rank'],
+ image:
+ `https://cdn.coryd.dev/artists/${artist['name'].replace(/\s+/g, '-').toLowerCase()}.jpg` ||
+ 'https://cdn.coryd.dev/artists/missing-artist.jpg',
+ url: artist['mbid']
+ ? `https://musicbrainz.org/artist/${artist['mbid']}`
+ : `https://musicbrainz.org/search?query=${encodeURI(artist['name'])}&type=artist`,
+ type: 'artist',
+}
+```
+
+This aligns `image` and `url` with the expected defaults in `normalizeMedia` with the remaining data being mapped inside of the `if (item.type === 'artist')` condition to align with the expectations of the `media-grid.liquid` template. Whew.
+
+Now, leveraging this looks like the following:
+
+{% raw %}
+
+```liquid
+{% render "partials/now/media-grid.liquid", data:artists, icon: "microphone-2", title: "Artists", shape: "square", count: 8, loading: 'eager' %}
+```
+
+{% endraw %}
+
+We use the [liquid.js render tag](https://liquidjs.com/tags/render.html) and pass in a number of things: `data` which accepts the data returned by our `artists.js` data file, an `icon` of our choosing, a `title` of our choosing, a `shape` (as artist/albums are square and our remaining sets are rendered as vertical images), a `count` of items to render and the `loading` strategy to leverage (`artists` load above the fold and, hence, adopt the `'eager'` strategy — the rest are below the fold and default to `'lazy'`). My full `now.liquid` file now looks like this:
+
+{% raw %}
+
+```liquid
+---
+layout: main
+---
+{% render "partials/header.liquid", site: site, page: page, nav: nav %}
+{{ content }}
+{% render "partials/now/media-grid.liquid", data:artists, icon: "microphone-2", title: "Artists", shape: "square", count: 8, loading: 'eager' %}
+{% render "partials/now/media-grid.liquid", data:albums, icon: "vinyl", title: "Albums", shape: "square", count: 8, loading: 'lazy' %}
+{% render "partials/now/albumReleases.liquid", albumReleases:albumReleases %}
+{% render "partials/now/media-grid.liquid", data:books, icon: "books", title: "Books", shape: "vertical", count: 6, loading: 'lazy' %}
+{% render "partials/now/links.liquid", links:links %}
+{% render "partials/now/media-grid.liquid", data:movies, icon: "movie", title: "Movies", shape: "vertical", count: 6, loading: 'lazy' %}
+{% render "partials/now/media-grid.liquid", data:tv, icon: "device-tv", title: "TV", shape: "vertical", count: 6, loading: 'lazy' %}
+This is a
+ now page, and if you have your own site,
+ you should make one too.
+```
+
+{% endraw %}