Migrating from Next.js to Astro
After running my website on Next.js for a while, I decided to migrate to Astro. Here’s what I learned along the way.
Why I Made the Switch
Next.js has been great, but lately I’ve been feeling the pain of the framework’s complexity and ecosystem chaos.
Dependency Hell
One of the biggest pain points with Next.js has been dependency management. Every major update seems to bring breaking changes, and the ecosystem of plugins, middleware, and tooling often lags behind. You upgrade Next.js, then something breaks. You fix that, and then npm audit fails with a dpeendency 5 layers deep. It’s an endless cycle of chasing compatibility issues and waiting for third-party packages to catch up.
The App Router Mess
The App Router deserves special mention here. While it promised to solve routing complexity, it introduced a whole new set of problems. Server components, client components, the confusing mental model of where things run, the restrictions on what you can and can’t do in each type of component—it all adds up to a lot of cognitive overhead for what should be a simple website.
What’s worse is that adding a "use client" directive at the top of a file can break your app in subtle ways that TypeScript won’t catch at compile time. You might import something that only works on the server into a client component, and TypeScript will happily say everything is fine. Then at runtime, boom—your app breaks. The lack of compile-time safety around the client/server boundary is incredibly frustrating.
The Good Stuff
Easier Upgrades
Astro upgrades have been refreshingly smooth. The framework has a smaller surface area and fewer dependencies to wrangle. When I upgrade Astro, things just… work. It’s a nice change of pace from the Next.js upgrade treadmill.
Faster Load Times
The performance improvement was immediately noticeable. Astro ships zero JavaScript by default, only adding it when you explicitly need it. My blog posts are mostly static content, so there’s no reason to ship a full React runtime to every visitor.
Overall Experience
So far, I’m really liking Astro. The component syntax feels familiar, and the mental model is much simpler. You write components, they render to HTML at build time, and you only add interactivity where you actually need it. No more worrying about server vs. client components or whether something will hydrate correctly.
The Quirks
That said, there are a few things that took some getting used to.
Conditional Element Types
One thing that’s not as easy as in React is conditionally rendering different element types. In React, I could easily do something like:
const Element = isLink ? "a" : "span";
return <Element {...props}>Content</Element>;
In Astro, you can’t dynamically choose which HTML element to render as cleanly. You end up writing more explicit conditionals, which can feel verbose when you’re used to React’s flexibility.
One Component Per File
Astro enforces one component per file. In React, I often defined small helper components in the same file as the parent component. While this restriction probably encourages better organization and reusability, it can feel annoying when you have a tiny component that’s only used once. It’s probably good practice in the long run, but it does slow you down initially.
Final Thoughts
Despite these minor inconveniences, I’m happy with the migration. Astro feels like it was purpose-built for content sites like blogs, whereas Next.js tries to be everything to everyone. The freedom from dependency hell alone makes it worth it, and the performance benefits are hard to ignore.
If you’re running a content-heavy site on Next.js and feeling the framework fatigue, I’d recommend giving Astro a look. The migration might be smoother than you think, and you’ll appreciate the simplicity.