Goodbye Jekyll, hello Astro

Last summer I became aware of Astro, a static site generator that promised a great developer experience and performance. I gave it a go and thought it was very promising, but it was missing a critical feature of my existing blog generator, Jekyll. This year, they released v2.0 with the feature, and I’ve been so impressed with the framework I had to migrate my whole blog!

Okay, I’d be lying if I didn’t say that the other reason for switching is because I found installing Ruby and Jekyll on a new laptop an intimidating process. There are a bunch of version managers for Ruby now — RVM, chruby, rbenv… I simply wanted to update the year in the footer from 2022 to 2023. There was also the problem of gulp.js being MIA for years now, and modern Node support is found wanting.

The feature that was missing from Astro last year was collections, and the ability to host collections on any URL route. Previously, only file-based routing was available. Now I can organise my portfolio and blog posts into sub-folders however I want, just like in Jekyll, and generate routes programmatically.

What really won me over is the developer experience. The documentation is great, it builds really fast, and you can essentially use any rendering framework you like, or you can stick with Astro’s own format, which is very capable and familiar if you use React.

I can write blog posts in MDX with JSX components (not possible in Jekyll), and non-HTML routes are nice and simple. Here’s what a /sitemap.xml could look like as a sitemap.xml.ts file:

import { getCollection } from 'astro:content';
import { sitemap } from 'xast-util-sitemap';
import { toXml } from 'xast-util-to-xml';
import { site } from './site.config';

function href(path: string): string {
  return new URL(path, site.url).href;
}

export async function GET() {
  const posts = await getCollection('blog');
  const pagination = Math.ceil(posts.length / site.pageSize);

  const tree = sitemap([
    // Named routes
    site.url,
    href('/about'),
    href('/tags'),
    href('/search'),
    // Blog posts
    ...posts.map(post => ({
      url: href(post.slug),
      modified: post.data.date,
    })),
    // page1, page2, page3 etc.
    ...[...Array(pagination)].map((_, i) => href(`/page${i + 1}`)),
  ]);

  return new Response(toXml(tree));
}

That’s another thing, it all works in TypeScript with all the benefits that brings, including type safety for frontmatter fields in blog posts (e.g. post.data.date is of type Date).

Search, tagging, and photo grids are carried over mostly the same, as described in my tech walkthrough. photoset-grid has been replaced with flexbox styles, which can easily do the same job natively now.

Speaking of which, CSS has improved greatly since 2015, and all the horrible float code I used has been banished.

It’s been great having a personal project to work on again, there’s certainly something liberating about not having to go through PR review. I just want to find more things to build with Astro. I swear this post isn’t sponsored! 😅