Really impressive demo. First, this is for single-page applications. Second, there is an API for multi-page applications. Check it out in Chrome Canary and look at the code. I discussed this with my team yesterday. The demo is built on Astro. All that is shipped to the browser is 301kB. Of that 291kB is images. Less than 5.5kB for the document, CSS, and JS. CSS is powering the transitions and only a bit of JS intercepts the navigation event, loads the fragment of HTML, injects it into the DOM, and adds the necessary classes to trigger the animations.

This is a truly impressive demonstration. With very minimal effort, one can use an SSG like Astro— which can run as an SSR too— and deliver a fully working application that requires no JavaScript but progressively enhances to dynamic page transitions with easy— something that is extremely difficult even for SPA libraries— and asynchronous page loading. Only 150 lines of JS are in this project— 150 lines that ship to the browser.

For an old curmudgeonly standards guy like myself, this gives me some hope that we can get back to the days of the largest assets we send to the browser are images instead of hundreds of kilobytes of JavaScript.

Source: Bramus

Something non-designers understandably struggle with is how to make things look good. One of those things is long form content that’s well set and readable. Luckily, CSS makes this easy, you just have to know what to change. That’s exactly what we’re going to teach you in this article.

Set Studio

So many good tips in this article from Andy Bell. I’m using many of them already on this site. Long-form content can be hard to format, so if you need some help, tap the link above!

Wrote this five years ago. Then it was Bootstrap and a bunch of JavaScript ninjas that didn’t know HTML/CSS. Now it’s Tailwind and a bunch of React ninjas that see HTML as an implementation detail and CSS as fundamentally broken.

Between the various CSS-in-JS solutions and the use of massive UI frameworks— looking at you MUI— we have taken frontends that could be lightweight, fast-loading, and enjoyable and made them into monstrosities of slowness.

I was looking at a website for a development agency in Atlanta last night and my 8 year old laptop got so hot the fans had to spin up. The website wasn’t even doing much beyond showing some content with some simple animations. I could understand not being able to run the latest games on my laptop but content-centered websites shouldn’t be doing this.

When every new website on the internet has perfect, semantic, accessible HTML and exceptionally executed, accessible CSS that works on every device and browser, then you can tell me that these languages are not valuable on their own. Until then we need to stop devaluing CSS and HTML.

Mandy Michael

Preach! It’s all I see these days in job descriptions. JAVASCRIPT! Ninja preferred. Rockstar acceptable. And then they produce crap frontend code. Their HTML and CSS is restricted to Bootstrap at best, custom crap with style attributes all over the place at worst. When I was their age, the emphasis of frontend was on the other side of the triangle: HTML and CSS. Without these, your JavaScript means nothing. Even if you are embedding your CSS and HTML in your JavaScript. Shudder.

Astro Build Performance

Sixty-five seconds just to build out the first pages of the #tech pages. Why? Because they are reliant on a complex function that gets a tag map. So let’s understand how pages are built for a route, from what I gather.

function doComplexThing() {
	const things = [];
	// do the thing

	// return the data
	return data;

The getStaticPaths method returns an array of pages and their data. This is called once. So that complex function? Not really a problem here. Then Astro renders each page that getStaticPaths returns. The problem is that I need that complex function’s data here too. Without calling it here, we build these pages in 10 seconds. With it, 65.

export async function getStaticPaths() {
	const things = doComplexThing();
	return => ({
		params: {
			tag: thing

Okay, now we have options. We could just put that data in the paths’ props. But it is used by a component in the page that is rendered. We could pass that data as a prop to that component. But it would be easier to call it inside the component. 65 seconds. And that’s just a fraction of the pages that need to build.

And that leads to an old trick from my PHP days. Caching the data of a function and returning it the next time we try to access it.

export let cachedThings: any[] | undefined = undefined;
export function doComplexThing() {
	if (cachedThings) return cachedThings;
	const things = [];
	// do the thing
	// cache it
	cachedThings = things;

	// return the data
	return data;

Now when we call doComplexThing() the first time, it runs the complex code. But then we hit it hundreds of more times and returns the cache.

There are many reasons to love the return of blogs. Folks taking the time to make a well-reasoned argument without truncating it to 140 characters is one of the biggest. And boy, William makes a great point here. Aside from the obvious spelling mistakes.

Clouds, and VPS’s before that, work on the age-old principle of buying in bulk and selling by the piece. You run one big server for $1,000/month, then you rent it out to seven people for $200/month, and voila, you’ve cleared a $400/month profit.

DHH, Don’t be fooled by serverless

This is something that has bothered me for some time. The prices of these services when compared to full servers is absurd. Hear that whole sentence. Compared to full servers. The response of many would be either a) “the prices are very low!” or b) “no one needs a full server!” The whole sentence matters. If you need a full server, the price is very high to rent it piecemeal. If you are going to eat a whole cow in a year, buying it pound by pound from the grocery store will cost substantially more than buying a whole cow.

But what happens if a customer needs the performance of a whole box, most of the time? Then they’re paying $1,400/month for $1,000’s worth of computing. Or maybe, because they’re reserving the whole box, they’ll get a deal at $1,250/month by committing to a whole year. That deal is far less obviously good on both sides. It’s basically a credit agreement at a 25% APR. Tread wisely!

But if you execute enough functions to fill the computing power of a whole box, it’s a terrible deal.

And then there is the lock-in. If you build an application in PHP or Ruby, you can basically run that anywhere. These cloud services are designed so you have to architect around them. If the pricing of my server for this site goes too high, I can take it elsewhere. It’s just HTML.

The further down the rabbit hole you go with “cloud-native” services in serverless, the harder it’ll be to climb out when you realize that you should own the donkey rather than rent it. And especially once you realize that paying to rent a whole donkey at the piece price of a hundred slices is an even worse deal than just renting the whole donkey by itself! […] And if you start off with a proprietary serverless setup, you might well find the lock-in impossible to escape by the time the rental math no longer works.

So who are these services best suited for?

The cloud is primarily for companies that have big swings in use – like Amazon’s original AWS case of huge demand around Black Friday and Christmas, which left them with unused capacity for the rest of the year – or for early outfits that don’t do enough business to either warrant owning a whole computer or spend so little on the cloud that it just doesn’t matter.

Front Matter CMS

With the relaunch of Finley, I am. a couple of weeks ago I shifted from using a CMS to everything-is-code. My blogging days stretch back to the mid-2000’s and my having built my own blogging tool called Blog Wizard. Various versions of that powered several blogs until I launched Finley, I am. in 2015 on Ghost.

Simply put, I am used to having a CMS. Code is great, but certain things are nicer with a CMS. Managing data things, specifically. The week with the relaunch I had imported all the articles from Ghost 1-to-1. Same tags and everything else. Last Sunday I started refactoring the tags. Because everything-is-code and I realized that I could build something cool if my tags were better. As detailed in the linked article, I merged and deleted a ton of tags. Over 300 tags in over 400 posts.

That brings us to Front Matter CMS. First, it’s a plugin for VS Code. Many CMSs exist for SSGs like Astro that write code. This sits alongside your Markdown post, living in the sidebar of Code. For SEO-minded folks, it provides SEO status info— like title, slug, article length, keyword management, etc. For me, it’s the publishing date, draft status, and very much the tag management for articles I love. Autocomplete on tags will help you remain consistent on your tag use, which I then use for finding similar posts.

And it helps you manage. The Dashboard shows all published posts and easily helps you find and edit your drafts. Taxonomies? Yeah, merging, renaming, deleting, and more. And remember, everything-is-code, so changing merging “video” into “videos” results in 10 changed files that you then commit and push.

If you use Hugo, Jekyll, Astro, or other SSGs that use front matter for metadata around posts, go grab Front Matter CMS and give it a shot.

Scroll Snap for a Nice Mobile Experience

Small enhancement to homepage. Two column layout, so mobile normally would make that one column. Which it had. But I write a few featured posts and lots of “other” posts. The sidebar had the “other” posts, but that got put way down the page. You’re unlikely to get there through scrolling. But putting it first wouldn’t make sense because then the featured posts are too far.

So… I made a scroller using the new scroll-snap CSS functionality. Now you can see the latest “other”, but it doesn’t push the featured posts down 3 scroll heights.

.b--article-list {
	display: flex;
	width: calc(100vw - 64px);
	overflow-x: scroll;
	scroll-snap-type: x mandatory;
	scroll-padding-right: 20%;

	> * {
		min-width: calc(80% - 32px);
		padding-left: 32px;
		scroll-snap-align: start;

With scroll-snap-type set to x mandatory, we allow card-based scrolling horizontally. Setting the scroll-snap-align to start for all children makes the snap point the left side of each card.

I have implemented this style of interface before, almost a decade ago. It took a lot of JavaScript. Now with just a few lines of CSS, you get a super interactive little component.

Demo: Homepage view on mobile or scale down and you’ll see the Ramblin’ section using the above.

Clamp for Truly Responsive Typography

CSS’s clamp() has been around for a few years now, but I finally got to use it in the rebuilding of this site. Let’s look at the hero text block on my new homepage!

:root {
  --font-size--dynamic-headline-3: clamp(21px, 4vw, 36px);
  --vertical-spacing-5: clamp(36px, 8vw, 72px);

.b--homepage-hero {
  max-width: 60ch;
  margin: 0 auto var(--vertical-spacing-5);
  font-family: Parkly, sans-serif;
  font-size: var(--font-size--dynamic-headline-3);
  text-transform: uppercase;

  & p {
    color: rgb(0 0 0 / 60%);
    text-align: center;

    & strong {
      font-family: "Parkly Wide", sans-serif;
      color: rgb(0 0 0 / 75%);
      text-transform: uppercase;

I’ve got the two variables included too. Okay, so the Dynamic Headline 3 variable is defined as clamp(21px, 4vw, 36px). Minimum, preferred, and maximum. The browser then makes the decision for us. Now one has to be mindful of that middle. It can be a somewhat arbitrary relative value if your clamp is tight, but if you have multiple headlines of varying sizes— like any typographer is going to— you need to make these numbers proportionate. Let’s look.

:root {
  --font-size--dynamic-headline-1: clamp(43px, 8vw, 72px);
  --font-size--dynamic-headline-2: clamp(32px, 6vw, 54px);
  --font-size--dynamic-headline-3: clamp(21px, 4vw, 36px);
  --font-size--dynamic-headline-4: clamp(21px, 3.1vw, 28px);
  --font-size--dynamic-headline-5: clamp(21px, 2.65vw, 24px);
  --font-size--dynamic-copy: clamp(16px, 2vw, 18px);
  --font-size--dynamic-small: clamp(14px, 1.78vw, 16px);

Dynamic Headline 2 is 50% larger than Dynamic Headline 3. That applies to the min, pref, and max. But at the smallest sizes, Dynamic Headline 3, Dynamic Headline 4, and Dynamic Headline 5 are all the same. They got too small otherwise.

As we allow our pages to be more fluid with dynamic grids, flexbox, and clamps, we have to let go of some of our pixel precision in design. Almost all the fonts— and vertical spacing!— on this site are dynamic with clamp(). The header, I think, is the only thing that isn’t.

Nothing too big, but I tweaked the “Other Posts You May Enjoy” section to sort by number of matches, not just date. Before it found all posts with one or more of the current post’s tags, but didn’t change the default order of reverse chronological. Now it sorts by number of similar tags, then date. So a post with 3 matches from last year will rank higher than a post with 2 from last week. Nothing ground breaking, but hopefully will help surface more relevant posts.

export async function getSimilarPosts (post: CollectionEntry<'posts'>): Promise<CollectionEntry<'posts'>[]> {
	const excludedTags = ['notes', 'links', 'featured', 'videos', 'quotes']
	const similarTags = => !excludedTags.includes(tag))

	return (
		(await getPosts(filteredPost => (
			filteredPost.slug !== post.slug && => similarTags.includes(tag)).length > 0
		))).map(post => ({,
			similarTagCount: => similarTags.includes(tag)).length
		})).sort((a, b) => {
			if (a.similarTagCount > b.similarTagCount) return -1
			if (b.similarTagCount > a.similarTagCount) return 1

			if ( > return -1
			if ( < return 1

			return 0