Astro-nomical Gains: Why I Switched to Astro from Next.js

I recently revived my long-dormant website as an excuse to better understand Keystatic, as well as the new Next.js app router, and server components.

A few months have passed since then, and I've rebuilt the site again, this time using Astro. This post explains why.

My personal website is a space to learn new things and over-engineer the hell out of what is essentially a pretty basic blog (that, up until very recently, I haven't really written anything for). This is the third design of my website, and it's the fifth framework I've used. Here they are in order:

  1. Hugo
  2. Gatsby
  3. Remix
  4. Next.js
  5. Astro

I've had something on this domain since 2019, that's roughly one framework a year. So why did I drop Next.js after just a few months?

I actually really like Next.js; it's my go-to in my day job. However, Next 13 has its issues. For the most part, I like the new app router, and I've wanted nested layouts in Next.js ever since I got a taste of them in Remix. The new app router, however, is slow. So slow that the Next.js team felt they had to publicly address it on their blog. I fear that the app router won't be practical for medium-sized websites and above until Turbopack becomes stable. As of right now, I can't use Turbopack on any of the websites that I've built with Next.js, so a stable release may still be some time away.

Server components also, on the whole, seem to be a great addition. They let me colocate fetching data with the components that use them, and they can also be leveraged so you can server render components that would otherwise require shipping large libraries to the client. Using server components usually results in a lot less JavaScript making its way to the client, which is always a good thing.

Apart from being one of the main reasons that the Next.js app router is so slow, they're also a little awkward to work with sometimes. Server components only run on the server and therefore can't have any state. This means we can't use any hooks in them, and it turns out they're a pretty big part of React!

When I'm building a new page for a website, I typically keep all of my components in the same file until either one of two issues comes up:

  • I have to use a component on another page
  • The page becomes too big to be able to quickly find the section I'm looking for

With server components, if I need some state (or even if I just want to generate a unique id for something), I have to extract that out into a new file. A lot of the time, these components aren't really reusable and are very page-specific, so putting them in a components folder feels wrong, and co-locating it with the page is also kinda weird because Next.js has some file naming conventions, so it's sometimes not clear if that file will be treated as "special" by Next.js or not. I typically add an underscore to the beginning of the filename of page-specific client components, which is fine, I guess ¯_(ツ)_/¯

Were any of these a deal breaker for me? No. In fact, I like using bleeding-edge stuff for non-critical stuff so that I have a good idea of when it's ready to be used for client work.

I actually did most of the work of porting the site to Astro about a month ago, but I decided I didn't like it enough to switch.

The full page refreshes didn't feel very nice, and I lost the animation in my navigation. My theme switcher didn't work properly and would require rebuilding. The react-tweet library didn't work properly for the handful of links to tweets I had wired up. I also don't love the Astro template syntax. It's just close enough to React to be annoying when it doesn't work how I expect it to. There are also a bunch of Astro-specific things like the numerous template directives that have me reaching for the docs a lot. It feels like the API surface of Astro is bigger than with Next.js or Remix, but maybe that's just because I'm still getting used to it.

So why did I switch to Astro?

There were a few reasons, all of them good, but the main reason I'm somewhat ashamed to admit was a purely cosmetic one is the stupidest of them all — View Transitions!

When Astro 3 came out with the stable View Transitions API, I decided to revive the Astro port and try it out. I added the <ViewTransitions /> component to the head of my document, and almost magically, all of my page transitions were super smooth, and the frustrations I had with Astro faded away.

The View Transitions fixed my issue with the navigation no longer animating, and I could drop framer motion here as the built-in animation was pretty much identical. This meant the only place I was using framer-motion was the animated steam on my coffee in SVG illustration on the home page. It took a bit of time to get right, but I was able to produce the effect I wanted with a keyframe animation (in fact, I think it looks better than it did before!)

I referenced Astro's new Starlight to learn how to implement a theme switcher. It wasn't as simple as using next-theme, but now I have complete control over the code for switching themes instead of relying on a library.

I got rid of the react-tweet library and inline the text of the tweets I was linking to. Twitter will probably go under soon anyway, so it's probably more resilient this way.

I still think Astro has some quirks, but I love the islands architecture. For most small to medium sites, it's probably the best choice. Sure, I removed a few libraries in the process of porting to Astro, but I'm pretty happy with the results.

Screenshot of the PageSpeed Insights website showing 99 for performance, 100 for accessibility, 100 for best practices, and 100 for SEO

For now, I think I'll stick with Astro, but who knows for how long.