Getting to Play with New Toys, 2022 Edition
It's been two years since I built this website, the urge to re-do it from scratch was irresitable.
These days I'm a member of a Frontend Platform team and staying up to date with the evolving technologies in the frontend space is very much a part of my job. I've incorporated reading articles and browsing open source code into my work routine, but it's hard to find the opportunities to try out all these new libraries and tools popping out.
But hey! I have this personal website that I hardly find the time to post to these days. I can turn myself into a cliché and start rebuilding it every two years to see what the cool kids use these days. Here we go!
New toys, volume 2
When I initially built the website, I chose a few (new to me at the time) technologes: Next.js, MDX and ThemeUI. You can read about my experience with these tools in Getting to Play with New Toys (2020 edition).
So what's on the 2022 menu? I decided to swap Next.js for Astro, ThemeUI for Tailwind. MDX stays. Let's dig into all these.
Framework - Astro
Having used Gatsby at work, then Next.js with SSG for the first iteration of Bits&Pieces, Astro seemed like the logical next step in getting leaner. It promises to get rid of all of the unnecessary JS shipped to the browser by both Gatsby and Next, and it delivers on that promise.
One of the selling points of Astro is its reliance on "islands architechture" - a way to only enhance small parts of the markup with client-rendered JS. This is in contrast to both Next.js and Gatsby, where the entire page markup gets rehydrated on the client, even if it never changes after it's statically built.
This feature of Astro really shined for a small feature I have on this website.
When you scroll to the bottom of this page, I use
Rough Notation to draw an outline around my name.
In my Next.js version of the site, I had to use a custom React hook with an
Intersection Observer to trigger the animation when the elements gets visible.
With astro, I just added a
directive to the Preact component which renders the outline - and that's it! 🤯.
One thing I miss from Next is their excellent build pipeline for images. I didn't even realise how much Next was doing for me there until I saw the difference in performance measurements after the initial migration. Astro does have an optional integration for image optimisations, but it's not as seamless to use as Next's built-in features.
Worth noting that while initial page load times, the Lighthouse score and Web Vitals are all impressive with Astro, this is only for the first page load. All further navigations within the website felt more instant with the Next.js version, since they were client-side rendered. With Astro's full-page reloads, even with the great performane of those page loads, things feel less instant and smooth. Astro has a prefetch plugin, which should help with this, but I havn't noticed much difference when using it.
One thing I'm a bit conerned when it comes to Astro is the huge surface for bugs
they have. Supporting rendering with all these frameworks (React, Preact,
Svelte, Solid, Vue...) means you should see bugs in integration with all those
frameworks too, and they do!
Having to account for so many setups with their own edge cases seems like a
monumental task. I was hit by one of these bugs, where custom CSS for my Preact
component worked in development, but
stopped working after the
build. Luckily, there was an acceptable workaround available (change from
Lastly, Astro comes with built in support for syntax highlighting using Shiki, which works great. Unfortunately, I haven't found a way to make it theme-aware with Tailwind, as the coloring is hardcoded at built time. This is something I solved with the previous setup (and shared some code for on this website), so having to skip it felt like a step back.
Styling - Tailwind
I've been putting off trying out Tailwind for way too long. After seeing it take the Web Dev Twitter by storm over the last few years, I felt compelled to give it a shot this time.
Transitioning from ThemeUI to Tailwind was actually quite easy. I've
already accepted having styling directly in
the markup (the
sx prop in ThemeUI vs the class soup in Tailwind). I only had
to do a few adjustments via the Tailwind config, adding custom fonts and a few
"brand" colors - I translated most colors to the standard Tailwind pallete using
a nifty online tool.
I now mostly understand the hype behind Tailwind. Using its tight integration with Astro there is almost no setup required, and the wins are obvious. No need to name things, lean and fast CSS output, good defaults and great documentation which makes the mundane task of lookup up class names for every single CSS property almost painless. Once you memorise most of the class names, building stuff starts feeling natural and void of indirections.
One rough spot in this migration was the integration with MDX. I used Tailwind's typography addon, which makes it easy to get something that looks ok, but fine-tuning it is quite messy. I ended up using the element modifiers API, which between light and dark themes and responsive styles added a whooping 40 CSS classes to my markdown wrapper element. This felt like a step back from ThemeUI, where the MDX support was actually the most elegant part.
Content - MDX
This one was a no-brainer. Since I've already ported all my content to MDX and Astro has first-class integration with MDX, there wasn't really a good reason to switch it. I was looking forward to trying Contentlayer to enable type-safe content access, but unfortunately it does not yet support Astro. I'll add it when the next re-write wave hits ;).
Some notable stats from the migration, mostly mesearued with pagespeed.compare a few times and averaged. Note that these are only for-fun numbers, comparing 2020 Next v9 with 2022 Astro v1.
- Lines of Code (excluding content): 1661 => 817
- Build time: 38 s => 9 s
- Largest Contentful Paint: 2.1 s => 1.6 s
- Time to Interactive: 3.25 s => 1.25 s
- CSS size (compressed): 1 KiB => 7 KiB
- JS size (compressed): 121 KiB => 17 KiB
- Unused JS size (uncompressed): 103 KiB => 11 KiB
- JS execution time: 750ms => 85ms
- Lighthouse score (mobile): 86 => 100