New Foundations: Rebuilding with Astro
Driven by mounting frustrations with Gatsby, I embarked on a quest for a suitable replacement. That's when I stumbled upon Astro: it wowed me with its blazing-fast performance, and amazing development experience.
Gastby turned out to be really frustrating to work with. First, its so-so support for ESM meant that keeping up to date with dependencies was quickly becoming a problem. Updating a dependency became a game of Jenga where one package could easily break the build. Doing upgrades of any kind became an exercise in pulling teeth. Then there was the issue of build time: Gatsby doesn’t keep a separate cache for processed images/assets. If you want a clean build, you essentially have to reprocess every single image. For me, that meant build times of over 30 minutes.
It was time for a replacement. I considered Next.js, but I quickly ruled it out as its image optimization pipeline doesn’t support Static Site Generation (it relies on third-party services to do the optimization at request time).
Astro showed up in my feeds in late 2021.
Their approach to interactive islands was innovative: at this time, SSG frameworks either fully hydrated the page as a client-side component on load (Gatsby, Next.js1), or you were left to fully implement your own client-side component hydration (Jekyll, Hugo). Astro’s interactive islands sit right at the middle. They also have first-class TypeScript and ESM support, something that Gatsby was struggling with at the time (and still is in the case of ESM).
It continued evolving over the years. In January and March this year, Astro had really important releases that brought two features that I needed: Built-In Image Optimization2 and Content Collections. All I needed now is time…
Astro’s developer experience is absolutely excellent, and the framework has close to everything that I needed to rebuild this website.
Astro Components are written in a JSX-like syntax, which allowed me to mostly port 1-to-1 all the React components that
were powering the rendering of the previous iteration. The main difference between the two is the
className prop, which now becomes
class:list depending on the use case.
Working with content collections is also a breeze. No more GraphQL queries! A simple
await getCollection('collection-name'), and you have all your records to do so as you please. Validation is handled by Zod,
which makes it really easy to both define your schema, and infer types from your schema (
z.infer<typeof schema>), including references
to other entries and asset files. If you have specific sorting requirements, you can simply use
Array.sort(). Simple and effective!
Image optimization also worked fine, but the pipeline is less polished than Gatsby’s. I ended up developing my own
<Picture /> component to automatically generate blurry loading placeholders (using
plaiceholder), sizes, and
formats. Astro does expose a
getImage() function to convert/resize images, making the last two trivial. Placeholders did require me to
write a custom integration, however, as the references returned by Astro for assets refer to the destination files, not to the source
I also replaced the previous syntax highlighter with Shiki. Theme switching and inline code highlighting in MDX files is handled by a custom remark plugin. Astro does have built-in support for Shiki. However it doesn’t at this time support theme switching or language detection in inline code blocks.
It ended up taking me a little less than 32 hours to reimplement everything with Astro. As for the build times, well… 😅
[build] 53 page(s) built in 9.32s
I did manage to add one really nice feature by leveraging Astro’s endpoints. Social share images are now automatically
generated for each post using Skia and
canvaskit-wasm. For example, this is this post’s OpenGraph share image:
More on that in a future blog post 😊.
I did encounter some issues. They are fairly minor in the grand scheme of things.
There is currently a bug in Astro’s language server that prevents it from correctly identifying Astro components passed as props other than
children in a React component. I ended up opening a GitHub issue, and it will eventually be fixed.
The other issue I had was with JetBrains’ Astro support. There’s no other way to put it, their plugin is absolute shit.
It randomly marked my props as deprecated, it struggled to understand what
boolean meant in an interface definition within an Astro
component. Just don’t use it. I switched to Visual Studio Code for now. Hopefully JetBrains will put the required time in to fix
I’m really, really happy with Astro. I’ve managed to migrate both this website and finewolf.gg in less than a week. I’m really looking forward to working it with some more in the future.
A huge thanks to the entire Astro team for your work on this, and for making yourself available for questions in the official Astro Discord.