sc
Scott avatar
ttwillsey

Astro RSS Compiled Content

Part of the Astro series

It’s been awhile and I have lots of news, but just a short one today: Astro now supports full RSS feed content if you use md files for your content. It works like this:

src/pages/rss.xml.js
export const get = () =>
rss({
title: config.get("title"),
description: config.get("description"),
site: config.get("url"),
items: Array.from(episodes).map((episode) => ({
title: episode.frontmatter.title,
link: `${config.get("url")}${config.get("episodes.path")}${
episode.frontmatter.slug
}`,
pubDate: rfc2822(episode.frontmatter.pubDate),
description: episode.frontmatter.description,
customData: `<enclosure url="${config.get("episodes.audioPrefix")}/${
episode.frontmatter.audiofile
}" length="${episode.frontmatter.bytes}" type="audio/mpeg" />`,
content: sanitizeHtml(episode.compiledContent()),
})),
});

See the last line of code?

content: sanitizeHtml(episode.compiledContent());

That’s directly telling Astro RSS that for a given item, the content is equal to the post’s compiledContent property (and run through sanitize-html for good measure).

You can find the Astro docs for it here: Including Full Post Content

There is one caveat I need to mention that directly affects this site. If you use mdx instead of md for your posts like I do here, compiledContent() doesn’t work. Since I don’t like my current RSS feed generation tweak for this site in order to get full content RSS items, my plan is to work on converting back to md and figuring out a way to process images such that I get the benefit of Astro Image’s Picture and Image components while using standard markdown files.

The Mastodon Webfinger Domain Search Super Trick

I promised an article about whether or not Mastodon and the Fediverse were going to solve all our problems and make us happy humans with a long species survival probability, but work and other tech projects have intervened. More on that later.

In the meantime, if you ARE on Mastodon and don’t want to run your own instance but would like people to be able to search for you there using your own domain name instead of the domain name of the instance you’re on, Maarten Balliauw has a really cool trick for this:

In other words, if you want to be discovered on Mastodon using your own domain, you can do so by copying the contents of https://<your mastodon server>/.well-known/webfinger?resource=acct:<your account>@<your mastodon server> to https://<your domain>/.well-known/webfinger.

You can read Maarten’s post on the webfinger method on his blog.

Title Case

Part of the Astro series

A good system should never make you remember its implementation details in order to use. That extends to blogging platforms. Since my blogging platform is a self-created, self-hosted website built on Astro, the onus of making myself not have to remember how it works just to use it rests solely on me.

Today I started writing a post about Mastodon and the Fediverse and why it’s not the solution to all your problems that you might think it is, and I realized I couldn’t remember if I write my blog post titles in Title Case or Just the first word capitalized or in camelCase.1 It’s not the first time I’ve had to ask myself this question, and that means it’s a problem I don’t want to think about anymore. So here’s a very simple Title Case generator:

export function titleCase(str) {
return str.replace(/\b[A-Za-z]/g, (x) => x.toUpperCase());
}

Running the first paragraph of this blog post through it results in this:

A Good System Should Never Make You Remember Its Implementation Details In Order To Use. That Extends To Blogging Platforms. Since My Blogging Platform Is A Self-Created, Self-Hosted Website Built On Astro, The Onus Of Making Myself Not Have To Remember How It Works Just To Use It Rests Solely On Me.

So now my blog post titles are generated like this:

<h1>
<a
href={new URL(
path.join(config.get("posts.path"), content.frontmatter.slug),
config.get("url"),
)}
>{titleCase(content.frontmatter.title)}
</a>
</h1>

Speaking of camelCase, I also took the opportunity to add a camelCase function that outputs what we called camelCase when I was a juvenile programmer-wannabe.

export function camelize(str) {
return str
.toLowerCase()
.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
}

I’ll be back next time with some reasons why Mastodon and the Fediverse aren’t suitable for most people in their current incarnations.2

Footnotes

  1. You modern whippersnappers probably think ThisIsCamelCase, but when I was a boy, thisWasCamelCase. So put that in your programming pipe and smoke it.

  2. Hint: it’s not a technical problem, it’s a people problem.

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

Part of the Astro series

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

Part of the Astro series

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.