Rebuilding My Website: From Next.js to Astro

Published - 6 min read

Why change something that isn’t broken? That’s what I thought for a long time, but the idea of improvement was always at the back of my mind. After trying Cursor, a new AI code editor, I realized how much I could streamline my development process. So, I decided to give it a shot - after all, how much work could it be?

As it turns out, still quite a bit.

The Migration Scope

This project was more than just a blog migration. It included interactive tutorials, small projects, and tools that I use regularly.

The migration involved several major changes:

These changes were driven by two main goals: better performance and improved maintainability.

Results

The most significant achievement was eliminating 11k lines of custom code, primarily from custom remark plugins that were replaced by Astro’s built-in functionality.

Another major improvement was the reduction in JavaScript bundle size:

This 42% reduction in bundle size directly translates to faster load times and better user experience. What makes this even more impressive is that I added more features and interactivity in the process, making the actual improvement even more substantial.

Cool Technical Highlights

AI-Powered Migration

One of the most fascinating aspects was leveraging Cursor to automate the conversion of JavaScript React components to TypeScript SolidJS.

My systematic process for each component was:

  1. Use Cursor to convert to TypeScript
  2. Review and debug the conversion
  3. Use Cursor to convert to SolidJS
  4. Review and debug the final output

Cursor achieved a 90% success rate in JavaScript to TypeScript conversions.

For SolidJS conversions, the results were mixed. While it handled simple components flawlessly, it only achieved a 40% success rate with complex/lengthy files. This limitation largely stemmed from the fundamental differences between React’s hooks and SolidJS’s signals paradigms.

Doing this manually would have been a nightmare. It would have been nice to do this in bulk, but I wanted to make sure the conversion was correct.

Modern Animation Techniques

I decided to add some modern animation techniques:

It’s quite amazing to see that the field of web design improved so much over the last 2 years. I took a lot of inspiration from Jhey, Emil Kowalski and others.

Challenges and Learnings

Time Investment

The migration timeline was interesting:

This follows the classic 80/20 rule, where the last mile often requires the most effort.

Technical Hurdles

Font Performance

Font optimization proved to be more complex than anticipated. My initial approach of using an unoptimized variable font resulted in a hefty 189kB file size.

The optimization process involved several challenges:

I’ll be diving deeper into these optimizations in a future post.

The Modern Web Stack

Web development tools change fast, but one thing remains constant: its complexity.

Content Processing Stack:

The challenge here is that each tool has its own plugin ecosystem, and they don’t always play nicely together. For example, remark-smartypants is a remark plugin that does a bunch of typographic improvements, like converting straight quotes into smart quotes (e.g., "boring" to “boring”). But, MDX feeds it one AST node at a time, and smart quotes need to be converted in pairs, so it fails in links (Astro issue #4448).

A specific example I encountered was with code syntax highlighting. I wanted to use the same code blocks during server-side rendering and client-side rendering, which necessitates that the syntax highlighting gets done in Astro, and I can use SolidJS to make it interactive.

The pipeline looks like this, I think:

MDX text → remark → shiki-remark (remark plugin) → rehype → Astro → MDX components → SolidJS → Final Output

If I replaced the shiki remark plugin with a tree-sitter remark plugin, in Astro I only get the syntax highlighted HTML, which is not what I want.

If I do it in MDX, I get raw text, but embedded in <code> html tags, and I lose the language attribute.

I gave up on this for now and just use the shiki remark plugin, but I believe to make it work, I will need a remark plugin to pass the metadata attributes like lang to MDX, then use tree-sitter parser in Astro during build time to get the AST, then feed it to SolidJS.

Build Stack:

The complexity arises when these tools interact. For instance:

A roadblock I hit here was improving the font performance. I initially used the most SEO-ed Astro font plugin, but it preloaded all fonts, which is suboptimal.

In order to do better, I forked the plugin and realized it was doing everything from scratch on every page load, including fetching Google fonts and parsing the variable font.

This could all be done during the initialization phase, so it seemed like an astro or vite plugin would be the “right” solution. As it turns out, to pass config from build time to runtime, you need to provide a virtual file in vite, then import it in an astro component. I also couldn’t simply modify the HTML <head>, because astro is in charge of generating the HTML during development, not vite.

In the end, I hit a realization: If I could let go of the idea of passing the config to the runtime, it’s a build-time problem. I just needed a build-time script to generate the font, CSS, and Astro file before anything starts. So I created a simple script that does that.

Looking Forward

This migration has been more than just a technical upgrade—it’s been a journey through the modern web development landscape. While the process had its challenges, the end result is a faster, more maintainable, and more feature-rich blog.

Stay tuned for more detailed posts about optimizations and the intricacies that went into it!