Friends With Brews

Peter and I decided a little while ago to convert the Friends with Beers podcast to Friends with Brews podcast. Our reasoning is pretty simple: we don’t always want to drink beer quite as often as we want to talk, and we both like coffee and tea.

But Clay Daly pointed out another huge benefit: the podcast is more inclusive as Friends with Brews, because some people won’t care about beer, but they might be interested in coffee or tea. And coffee and tea, you may be shocked to learn, are brewed. They qualify as brews. So there will be times on the podcast that we’re both drinking beer, or we’re both drinking coffee, or one of us is drinking beer and tea, or just tea, or just coffee, or… The permutations aren’t endless, but they may as well be for all the effort I’m going to exert in calculating them.1

Anyway, I think that the change will be seamless if you subscribed through Apple Podcasts or Overcast. If you subscribed directly to our RSS feed, then please delete it and resubscribe to the Friends with Brews RSS feed.

By the way, the Friends with Brews website is an upgrade. It features a fully indexed search capability, accessible both from the home page and from the brews page, which will return matches for the site in general and for the different drinks more specifically. I think it also looks a lot nicer than the previous incarnation of the site, but I am biased since I’m the one who built it.

Footnotes

  1. Hint: zero effort

Friends With Beer 2.0

I think I mentioned in my Responsive Image Rabbit Hole Series that I have been building an Astro version of the Friends with Beer website, and it’s finally live!

Friends with Beer now features a nicer look, redesigned beer detail pages, and a fully indexed search capability that returns results for episodes AND for beer! That means you can search for a beer we talked about in an episode and find it immediately. The search UI shows up on the home page and on the beer cooler page.

I still need to tweak the custom 404 by updating it for the new build, so it is basically an un-styled page at this point.

Having finished this, my work isn’t even done yet, because now I need to tweak the site to meet our new goal of being Friends with Brews! That’s right, we want the flexibility to drink not just beer, but coffee and tea instead. Those are brewed, beer is brewed, and it means less beer drinking for times a beer really doesn’t sound right.

More on that soon, hopefully…

Picture Element Sizes Attribute

A couple days ago, I posted some responsive image and Astro Image science experiments. If you read that post, you’ll notice that my examples all assume I want to display my image at 200px wide. The reason is I was performing the experiments on a very specific layout that I use to show what beer we enjoyed on a given episode of Friends with Beer.1

But the sizes attribute of the HTML picture element is a lot more versatile than that. You can get very granular in how you control your image display sizes. I’ll show how to do it using the Astro Image component, specifically its Picture component.

When I wrote the my science experiments post, I had all the images in the article set to display at 400 or 500px width, depending on the image. I debated about the size of some of them being too small to see the details very well, but the images link to the large original, and some of them are quite a bit taller than they are wide and I didn’t want to make a page full of posters.

But more troublingly, setting them with a simple sizes="500px" value made them look bad on mobile devices. For example, here’s my first image in the article shown in the iPhone 12 Pro screen size, and it just shoots out both sides like there’s no guard rails and no cliffs next to the road.

500px wide image on iPhone 12 Pro screen

It’s a little confusing, because it’s an image of me examining images on a web page in Chrome developer tools, but you can see that it’s indeed displaying at my requested 500px width and is too wide for the page on the iPhone as a result.

500px wide image details in Chrome developer tools

Fortunately, sizes has an answer for us: we can specify it to be 500px above a certain screen width, and then just show at 90% of the viewport width below that screen width.

<Picture
  src={image1}
  widths={[500, 1000, 1500]}
  sizes="(max-width: 550px) 90vw, 500px"
  formats={["webp"]}
  alt="600px wide image displayed at 200px width"
/>

It’s a little unclear how to read that initially. Here’s what it means:

  • For screens up to 550px wide, display the image at 90vw (90% viewport width).
  • For screens that don’t meet the specified condition (ie., they are wider than 550px), display the image at 500px wide.

It works great. Now for wide screens, I get a 500px wide image, and for smaller screens, I get an image better tailored to the display.

Image displaying at 500px wide on bigger screen
Image displaying at 90vw width on small screen

You can get even more granular. For example, I determined that an image I displayed in a different post at 600px wide on big displays worked better if it was only 80% of the viewport width between 650 and 750px screen widths, and 90% of the viewport width below that.

<Picture
  src={beerlatest}
  widths={[800, 1200, 1800]}
  sizes="(max-width: 650px) 90vw, (max-width: 750px) 80vw, 600px"
  formats={["webp"]}
  alt="Latest episode beer list view"
/>

You may be thinking “Wow, that’s cool!” and also thinking “Wow, I can’t believe you’re going to type all that out every time you want to include a 600 pixel wide image in an article!“.

Yeah. I’m not.

There are a couple different ways to fix that. Both involve me using markdown syntax for my images but adding a display width to it, like this:

![images/image3-1C3FC4F9-AB6B-44DA-B33A-377336BD42B9.png width="500"](images/image3-1C3FC4F9-AB6B-44DA-B33A-377336BD42B9.png)

I’ll also throw in an alt tag value for good measure, of course.

Using that, I have a couple options:

  • I can write a script to run in Drafts, my markdown editor of choice which has excellent scripting support, that converts all my markdown images to Astro Image’s Picture component format with widths and sizes attributes nicely calculated and filled in automatically for me,
  • See if using some kind of rehype plugin in Astro at site build time would be a better way to do it so I don’t even have to actually click a script button in Drafts.

Either way, I absolutely do NOT plan on having to remember the science experiments I did to come up with my sizes attribute values for different image widths. That just goes against the whole point of trying to keep the writing process separate from the need to know about mundane site details, and for me it’s a step backwards.

The end result, regardless of which way I decide to go about it, is that I just have to know markdown image syntax (I do), and how wide I want it to display in the blog post and what I want the alt tag to be. I’ll let a script located somewhere figure out the implementation details for me.

Make computers work for you, not the other way around!

Footnotes

  1. Depending on when you read this, the Friends with Beer website may still be on Eleventy. The Astro version is WIP.

Reponsive Image Rabbit Hole – Part 3

It’s been a few days since I’ve written anything, and I’m going to make my tardiness up to you with some detailed science experiments. You can find part 1 and part 2 of this series here and here.

This article will be helpful for understanding responsive images in general, but also the @astrojs/image Astro component for generating responsive images in particular.1 I’m not the fastest learner and it generally requires me to poke around at things awhile before I understand how they work, so browser developer tools are a must in my process.

First off, I owe Tony Sull, author of Astro Image, a public apology. In the midst of converting some sites of mine from Eleventy to Astro, I found my images on my astro sites to look really low resolution compared to what I was getting in Eleventy with the eleventy-image plugin. Failing to understand how responsive images work (but thinking I did) and failing to perform the right tests and sanity checks made me think I was writing equivalent code and getting different results. I even went so far as to complain about the image quality from Astro Image in the Astro Discord a couple of times.

But I was wrong. Astro Image works just fine. It’s me, myself, and I that wasn’t working just fine.

When using Astro Image to generate responsive images, two factors have to be taken into consideration:

  1. The differences between Astro Image’s Picture component and Image component,
  2. How browsers choose responsive image sources for the user’s screen resolution/pixel-ratio and viewport size.

Astro Image’s Picture component

I showed in Part 2 of the Responsive Image Rabbit Hole how the Picture component can be used to generate multiple sources with their own srcsets for any given image, and what the resulting HTML would look like. I showed how the Image component would generate one width of image based on your input at whatever quality and format you specified. In contrast to Picture, you get one image width and one image format.

I also talked about needing to take screen resolution and pixel-ratio into account in order to generate images large enough to look good on high resolution displays when displayed at the desired width.

The result is that making images of high enough resolution on a high pixel-ratio screen with the Picture component is easy.

For example, here I want 600, 900, 1200, and 1500 pixel-wide versions of the image in avif, webp, and png, and I plan to display it at 200px wide.

<Picture
  src={`/images/beer/${beer.image}.png`}
  widths={[600, 900, 1200, 1500]}
  aspectRatio="1:1"
  sizes="200px"
  formats={["avif", "webp", "png"]}
  alt={`${beer.brewery} ${beer.name}`}
/>

Checking the result with Chrome developer tools, the image is displayed at 200px wide and the image downloaded is the 600px wide version.

600px wide image displayed at 200px width

Let’s additionally generate 200 and 400px wide images to see which one gets downloaded to display at my desired 200px width.

<Picture
  src={`/images/beer/${beer.image}.png`}
  widths={[200, 400, 600, 900, 1200, 1500]}
  aspectRatio="1:1"
  sizes="200px"
  formats={["avif", "webp", "png"]}
  alt={`${beer.brewery} ${beer.name}`}
/>

The answer is that the 400px image gets downloaded to display at 200px wide. Why? Simple… My iMac has a screen with a pixel-ratio of 2. It wants a 2x image for any given display width.

400px wide image displayed at 200px width

Now let’s use Chrome’s responsive device mode and look at what happens on an iPhone 12 Pro screen with the exact same Picture component parameters as above, still generating image widths at 200, 400, 600, 900, 1200, and 1500px wide.

600px wide image displayed at 200px width

Now we get the 600px wide image in order to display it nicely, because the iPhone 12 Pro has a screen pixel-ratio of 3.

You can use the console in your browser developer tools to show you the pixel-ratio for your computer’s screen as well as any simulated devices in responsive mode. Below are the pixel-ratios for my 27” 5K iMac screen and the screen of the iPhone 12 Pro that I used for the above tests.

Mac window device pixel ratio
iPhone 12 Pro window device pixel ratio

The Mac has a device pixel ratio of 2, so it needs and receives the 400px wide image to display at 200px, and the iPhone has a device pixel ratio of 3, so it wants and receives a 600px wide image to display nicely at 200px. If you have a picture element in your page and you are getting a larger image than you expect given what you set for the image size in the Picture sizes attribute, check your display window pixel ratio. Chances are it’s higher than 1.

Astro Image’s Image component

The Astro Image Picture component lets us easily overcome this, but it’s not so clear how to do so with the Image component. Image component accepts one width in its width attribute, and it has no concept of sizes. Setting the width (and height and aspect ratio if needed or desired) determines the size of image generated.

This means either one of two things: You’re going to get a fuzzy image on high resolution displays if you set width to the actual display width you want, or you’re going to set it higher than your desired display width and you’re just going to get an image displayed wider than desired and that is still fuzzy.

For example, let’s throw an Image component in the mix. Let’s give it a width of 200px because that’s what size we want our image to be. Let’s look at it next to the unchanged Picture component with the settings we last used above.

<Image
  src={`images/beer/${beer.image}.png`}
  width={200}
  aspectRatio="1:1"
  format="webp"
/>
<Picture
  src={`/images/beer/${beer.image}.png`}
  widths={[400, 600, 900, 1200]}
  aspectRatio="1:1"
  sizes="200px"
  formats={["avif", "webp", "png"]}
  alt={`${beer.brewery} ${beer.name}`}
/>

All these images are being displayed at 200 x 200px, as desired. But only the ones from the Picture component are nice and sharp, because it’s using 400px wide images for those. The Image component is generating a single 200px wide image and using that, and it’s noticeably blurrier than the 400px wide 2x image.

Image and Picture components with 200px width

Ok, fine, you say. Give image a width of 400 and call it done. That is in fact the correct answer (assuming your site never gets displayed on a screen with a higher pixel ratio than 2), but guess what? You’re also telling it to generate an img tag with a width attribute set to 400. I’m sure it’s not going to surprise you to see what happens next:

Image component with 400px width

To add insult to injury, not only is it displayed at twice the size you actually want, it’s still fuzzy by comparison because it’s not acting as a 2x image. It’s just a bigger 1x image on a screen with a pixel ratio of 2. Wah, wah.

Astro Image’s Image component doesn’t generate srcsets like the Picture component does, even though we saw in Part 1 of this Response Image Rabbit Hole that the HTML img tag does support srcsets.

If you use the Astro Image component, you will need to generate an image large enough to look good on screens with pixel ratios of 2 and 3, and then use CSS to control the display size.

In order to conduct these experiments for you, I got rid of any CSS related to image size. I’m going to put img class height and width properties back in, like this:

.beer-image img {
  height: 200px;
  width: 200px;
  border: 3px solid var(--menu-surface);
  border-radius: 10%;
  aspect-ratio: 1;
  margin-bottom: -0.6rem;
}

The result is the image from both the Image component and the Picture component are displayed at 200 x 200px, and both use 400 x 400px images as source files. Both now look equally sharp on my Retina display.

Image component with 400px width using CSS to set the display size

Lesson learned

The lesson I learned is very simple:

  • Use the Astro Image Picture component and make sure to use sizes to control the display size and use widths to generate sizes that will look good on high density displays, OR
  • Use the Image component, set the width that will look good on screens that want 2x and 3x images, and control the display size with CSS.

Footnotes

  1. For simplicity, I’ll reference it as Astro Image.

Reponsive Image Rabbit Hole – Part 2

In installment 1 of this responsive image topic, I talked about how the modern approach to giving site visitors the best combination of image file size and image quality comes down to generating a bunch of versions of the image and letting the browser choose. Further, the browser chooses by being given a choice of sources and/or srcset elements using the HTML picture or img. So the two step process for making image optimization possible for site visitors is: 1) Make a bunch of image files for each image you will display, 2) Create the HTML that allows the browser to know about and choose from the available options.

This sounds like a lot of work to do whenever you want to drop an image in a blog post. Who wants to do all this every time? The correct answer is no one. Anyone who does this manually for every image they want to inflict on their visitors doesn’t understand that the computer is there to work for them instead of the other way around. Fortunately, all modern web frameworks understand this and have solutions in place to tackle image optimization.

Astro Image plugin

In the case of Astro, the official answer to this is the @astrojs/image plugin. For simplicity I’ll just call it Astro Image from now on. To understand what image optimization plugins do, the Astro Image documentation says this:

Images play a big role in overall site performance and usability. Serving properly sized images makes all the difference but is often tricky to automate.

This integration provides <Image /> and <Picture> components as well as a basic image transformer, with full support for static sites and server-side rendering. The built-in image transformer is also replaceable, opening the door for future integrations that work with your favorite hosted image service.

There are a couple key points here. One is providing Image and Picture Astro components. That means you can generate all the html you need with a component like this:

<Picture
  src={beerlatest}
  widths={[800, 1200, 1800]}
  sizes="(max-width: 800px) 95vw, 90vw"
  formats={["webp"]}
  alt="Latest episode beer list view"
/>

The resulting HTML will be the fully conceived HTML picture element with all the sources and srcsets you need. I added judicious use of carriage returns and tabs to make each of the elements more readable.

<picture class="astro-EI35XRNH">
  <source
    type="image/webp"
    srcset="
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_1mD09L.webp   800w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z1gGQwg.webp 1200w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z22UqRY.webp 1800w
    "
    class="astro-EI35XRNH"
    sizes="(max-width: 800px) 95vw, 90vw"
  />
  <source
    type="image/png"
    srcset="
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_wgsuf.png    800w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z2b2h6I.png 1200w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_1yzNhO.png  1800w
    "
    class="astro-EI35XRNH"
    sizes="(max-width: 800px) 95vw, 90vw"
  />
  <img
    src="/assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z8LQlw.png"
    class="astro-EI35XRNH"
    loading="lazy"
    decoding="async"
    alt="Latest episode beer list view"
  />
</picture>

You may notice the different file names for each image resolution in the srcset for each of the two sources. If you guessed that the second part of what Astro Image does is generate the different image files for the browser to choose from, you win a virtual round of applause. For each of the widths you specify in the widths attribute of the Astro Image Picture component, Astro Image will generate an image of that width for that source’s file type. For local images, all heights will be calculated to keep the original aspect ratio, while for remote images, an aspect ratio must be provided for Picture to know what height to use.

Astro Image also has an Image component which you can use to create resized images in whatever format you desire. However, there are some limitations to the Image component in Astro Image. You can only generate one size (it does not make use of the HTML img srcset attribute) and one format. This means you need to remember my warning about high resolution screens at the bottom of part 1. This means if you use the Image component, you are going to certainly want to specify a width of 2-3x the pixel width you plan to display the image at.

I use this for my About page selfie image. Below is the Astro component code followed by the resultant HTML.

<Image
  class="about-av"
  src={av}
  width={600}
  format={"webp"}
  alt="Scott Willsey"
  quality={85}
/>

<img
  class="about-av astro-AT6AUSG4 astro-UXNKDZ4E"
  alt="Scott Willsey"
  width="600"
  height="600"
  src="/assets/ScottLatest.cbf6b2e6_1ymKwq.webp"
  loading="lazy"
  decoding="async"
/>

I actually display the image at 300x300 (which I control in css) and it looks ok on high resolution screens because the image is 600x600.

The Retina wrinkle (again)

Remember last time when I said retina or high resolution displays throw a monkey in the wrench of displaying images? I fooled myself for a long time into thinking Astro Image wasn’t working correctly because I kept forgetting about it, even though I know very well about retina displays and their need for higher resolution images.

But now, because apparently I can’t quit using words, I’ll have to save that for part 3. I want to explain what I did on my Eleventy sites and what I was doing with my Astro sites, and how converting Friends with Beer from one to the other helped me understand my incredible ignorance about how all this works in the first place.

Stay tuned.

Reponsive Image Rabbit Hole – Part 1

I fell down a deep rabbit hole yesterday thanks to the fact that I’m converting the Friends with Beer podcast website from Eleventy to Astro. The rabbit hole was specifically image optimization, the effort to build responsive and hopefully smaller file size images.

Why image optimization?

Image optimization and how browsers can handle various methods of optimization is a pretty interesting topic. The basic idea is to give the browser options for any given image so that it can display them as intended by the site or article author, but with as little data transfer and image loading time as possible.

Browser variables that can affect image rendering efficiency are things like platform (mobile vs desktop-class browser), internet connection bandwidth, viewport size, screen resolution, and which image formats the browser supports. Ben Holmes wrote a great article on the topic of perfect image optimization that you should read which talks about some of these variables.1

How image optimization?

Ok, that’s not really proper English, but you get the point. Now we know we need to try not to send bigger images to the browser than necessary, but we still want them to look good. How do we do this?

The answer is: make multiple sizes and formats of each image and let the browser figure it out.

Modern browsers allow you to specify source sets for images. Given these image source sets, the browser can make a choice on which one it wants to request to perform the role of the image specified in the img tag.

Image source sets can be specified using either the HTML picture element or directly in the HTML img element itself.

HTML img element

With an HTML img, you can specify a srcset like this (example from MDN):

<img
  srcset="elva-fairy-480w.jpg 480w, elva-fairy-800w.jpg 800w"
  sizes="(max-width: 600px) 480px,
         800px"
  src="elva-fairy-800w.jpg"
  alt="Elva dressed as a fairy"
/>

In the above example, there are two jpg versions of the same image available, one 480px wide and the other 800px wide. The browser will download the image size that makes sense for it given the viewport size and screen resolution. The value of the sizes attribute specifies that if the viewport is 600px or less, you’ll get a 480px wide image, otherwise you’ll get an 800px wide one.

HTML picture element

The Picture element is a little more involved but also more versatile (again from MDN):

<picture>
  <source srcset="photo.avif" type="image/avif" />
  <source srcset="photo.webp" type="image/webp" />
  <img src="photo.jpg" alt="photo" />
</picture>

As you can see, instead of just one srcset, you can have multiple sources (one source for each image format option available), each with their own srcsets. These srcsets can in turn contain multiple image sizes. Here’s an example of this from my last post on this site:

<picture class="astro-EI35XRNH">
  <source
    type="image/webp"
    srcset="
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_ZFUDaL.webp   300w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z2uWKfV.webp  600w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_1mD09L.webp   800w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z1gGQwg.webp 1200w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z22UqRY.webp 1800w
    "
    class="astro-EI35XRNH"
    sizes="(max-width: 800px) 95vw, 90vw"
  />
  <source
    type="image/png"
    srcset="
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z1MMor.png   300w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_13Es8j.png   600w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_wgsuf.png    800w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z2b2h6I.png 1200w,
      /assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_1yzNhO.png  1800w
    "
    class="astro-EI35XRNH"
    sizes="(max-width: 800px) 95vw, 90vw"
  />
  <img
    src="/assets/BeerList-FCBA21C9-2F71-4051-B283-51452F68625D.d9a54970_Z8LQlw.png"
    class="astro-EI35XRNH"
    loading="lazy"
    decoding="async"
    alt="Latest episode beer list view"
  />
</picture>

This is an admittedly extreme example of generating 10 different images (5 sizes of webp, 5 sizes of png) just for one actual image on the website. I probably shouldn’t do this many resolutions in practice, and in fact I probably gain no benefit from doing this many. I probably only need 2 or 3 of those. But it does give you a good idea of the fact that each source in a picture element is a specific format of image, and inside that source, the srcset contains the different image sizes available for that format option.

The Sizes attribute works as it does with img, in this case specifying that up to 800px browser width, the image should be sized to take up 95% of the viewport width, and above 800px wide, only 90% of the viewport width. The context here is that my whole website content section is set to a maximum of 70ch or something like that, so even if you have your browser in fullscreen mode on a 5k iMac, the image will only be 90% of 70ch wide anyway.

The Retina wrinkle

Speaking of 5k iMac displays, there’s a wrinkle in this whole image optimization scheme: high-resolution displays (known as Retina displays in the Apple world). Basically for a given resolution, the screen uses double or triple the pixel density in order to display things sharp enough that the individual pixels can’t been seen by the human eye. What this means in terms of images on websites is that if you want to display a nice looking 800px wide image on a Retina display, you actually need a much higher resolution version of the image.

The image resolution issue was something I tripped over when fighting my image optimization strategy for both this site and the work-in-progress Astro version of Friends with Beer. I knew this fact but didn’t take it into account when I was looking at which size image was downloading for a given image. I thought the Astro Image component I was using was downloading a larger image than it should be given the size I wanted to display, but in fact the only thing that was broken was my understanding of how responsive images work.

I’ll tell that story in Part 2. In the meantime, here are some excellent links on image optimization.

Footnotes

  1. Ben now works for Astro, the framework that I use for this website and highly endorse.

Map Your Stuff

One of the patterns you’ll see frequently in Astro is using the JavaScript array map function. Array.map() creates a new array that holds the results of performing whatever function you provide on each element of the original array.

Ok, that’s clear as mud.

But let’s say you have a podcast. Let’s say this podcast is called Friends with Beer, and let’s say you have a json file full of information about the beer you drink on your podcast. Let’s say it looks like this, repeated n number of times where n is the number of beers you’ve had on the podcast.


[
  {
    "id": "OShp7ovkwb6F14mpRqFbw",
    "name": "Hell or High Watermelon",
    "brewery": "21st Amendment Brewery",
    "image": "21stAmendmentBreweryHellOrHighWatermelon-EA669A2C-D404-422C-8495-AA268674CAA5",
    "sortOrder": "0",
    "episodes": ["14"],
    "url": "https://www.21st-amendment.com/beers/hell-or-high-watermelon",
    "rating": [
      {
        "host": "Scott",
        "vote": "thumbs-up",
        "description": "I wish it had more watermelon flavor, but it is a nice light wheat beer that's very pleasant."
      }
    ]
  },

...

]

Presumably you’d like to show the latest episode on your site’s home page with a little view featuring the beer that was consumed on that episode, like this:

Latest episode beer list view

First thing you need to do is grab the json file and find all beer associated with whatever episode is the latest. I have a file named beerlist.mjs that exports a beerList function this because I want to be able to get a beer list in other places on the site too.

import beer from "../../data/beer.json";

export function beerList(episode) {
  const ep = episode ?? 0;
  let beers = Array.from(beer);

  return ep === 0
    ? beers
    : beers.filter((beer) => beer.episodes.includes(String(episode)));
}

I can optionally pass in an episode number to filter the list by. If I do, I return an array of the episode-filtered beers. If no episode number is provided, I just return an array of the full list of beers.

Now I can create that view from the image above by importing my function and using it like this:

---
import { Icon } from "astro-icon";
import { Image } from "@astrojs/image/components";
import { beerList } from "./utilities/beerlist.mjs";

const { episode } = Astro.props;

const beers = beerList(episode);
---

<div class="beer-container">
  {
    beers.map((beer) => (
      <div class="beer">
        <div class="beer-image">
          <a href={`/images/beer/${beer.image}.png`}>
            <Image
              src={`/images/beer/${beer.image}.png`}
              width="300"
              aspectRatio="1:1"
              format="webp"
              alt={`${beer.brewery} ${beer.name}`}
            />
          </a>
        </div>
        <div class="beer-name">
          <div class="brewery">{beer.brewery}</div>
          <div>
            <a href={`/bottle/${beer.id}`}>{beer.name}</a>
          </div>
          <div class="beer-details">
            <span>
              <Icon name="fluent:info-24-filled" />
            </span>
            <span>
              <a href={`/bottle/${beer.id}`}>View Details</a>
            </span>
          </div>
        </div>
      </div>
    ))
  }
</div>

The fun part is everything inside the map function. As you can see, my beerList function returns an array of beers. I map that so that for each beer in the array, I output the HTML inside the map function. This consists of an image of the beer, the brewery name, the beer name, and a link to view the page for that beer.

You can also make your maps more legible by creating a component to use inside the map. Here’s an example from the code for the paginated blog posts on this site that uses a Post component to do all the rendering of each post, just passing the individual mapped post to the component. It looks neater and is easier to understand, but it means creating another component. It just depends how much you want to break things down into separate components. If you need to show posts in a similar manner elsewhere besides the paginated list, you may want to do it by mapping your array items as props to a separate component, like below.

<Base title="test">
  <section aria-label="Post list">
    {
      posts &&
        posts.map((post) => {
          return (
            <Post content={post}>
              <post.content />
            </Post>
          );
        })
    }
    <Pager page={page} , pageSize={pageSize} />
  </section>
</Base>

The Astro docs have a good example of using the map function in the “Converting markdown to MDX” guide and (more usefully for most people) the Astro.glob function documentation.

By the way, if you’re wondering why I treat episode number as a number sometimes and treat it like a string other times (inside beer.json, for example), rest assured you aren’t the only one wondering. I took that json file from my existing Eleventy site for Friends with Beer and didn’t think much about it. Refinements are certainly a valid consideration.