- Published on
- · 8 min read
Why I Migrated My Blog from Next.js to Astro
- Authors
-
-
- Name
- Abdul Rauf
- @armujahid
-

This blog has been running on Next.js for years. It worked — but it was like driving a truck to the grocery store. Every page shipped React’s runtime to the browser just to render static text. The build chain required cross-env wrappers and post-build scripts. And the content engine, Contentlayer, was abandoned by its maintainer with no clear successor.
So I rewrote the entire thing. The result: half the dependencies, 5,000 fewer lines of code, near-zero client-side JavaScript, and a simpler deployment on Cloudflare Workers. Here is how it went.
Why Astro + Svelte?
Next.js is overkill for content sites. It is an excellent framework for interactive applications — dashboards, SaaS products, anything with complex client-side state. But a blog does not need React hydration, client-side routing, or server components. Every page on this site is static content with maybe a search box or theme toggle. Shipping a JavaScript framework to render markdown felt wrong.
Contentlayer was dying. The original project was abandoned. The community fork, contentlayer2, kept things running but it was a stopgap, not a long-term solution. I needed a content pipeline that was actively maintained.
I tried another path first. Before landing on Astro, I spent considerable effort migrating to Velite (a Contentlayer replacement) and then to Vinext (Vite-based Next.js). After 10+ commits battling Cloudflare Workers deployment issues — blank pages, 404s, new Function() being blocked by the Workers runtime — I abandoned the approach. The site technically worked, but the performance and accumulated workarounds were not acceptable. Read about the Vinext migration.
Astro ships zero JavaScript by default. For a blog, HTML-first beats JS-first. Instead of shipping a full kitchen to your browser, Astro sends the finished meal. And because everything is fully static, there are no Cloudflare Workers runtime issues — it is just serving files.
Svelte compiles away. Unlike React, Svelte has no runtime library. The compiler turns components into minimal vanilla JavaScript at build time. I only use Svelte for a handful of interactive islands: search, theme toggle, and mobile navigation. Everything else is .astro components, which produce zero client-side JavaScript.
Both are fast. Astro’s build is fast, Svelte’s output is tiny, and pages load instantly. The combination gives you the developer experience of a component framework with the performance of a static site.
What Changed
| Area | Before | After |
|---|---|---|
| Framework | Next.js | Astro 6 |
| UI Components | React | Svelte 5 + Astro |
| Content | Contentlayer2 | Astro Content Collections |
| Styling | Tailwind CSS (PostCSS) | Tailwind CSS v4 (Vite) |
| Deployment | Vercel | Cloudflare Workers |
| Analytics | @vercel/analytics | Google Analytics via Partytown |
| Dependencies | 54 packages | 28 packages |
| Dev Dependencies | 18 packages | 2 (TypeScript + Wrangler) |
A few highlights:
Content Collections replaced Contentlayer with built-in frontmatter validation — no third-party content engine needed. Astro validates your frontmatter against a Zod schema at build time, catching errors early instead of at runtime.
Cloudflare Workers serves static assets globally on the edge. The free tier is generous, there are no cold starts, and deployment is a single wrangler deploy command.
Partytown moves Google Analytics off the main thread entirely. The analytics script runs in a web worker, so it never blocks rendering or interaction.
Page transitions use Astro’s ClientRouter, which leverages the View Transitions API for smooth SPA-like navigation between pages — without shipping a client-side router.
The build command tells the story on its own:
# Before (Next.js)
cross-env INIT_CWD=$PWD next build --webpack && cross-env NODE_OPTIONS='--experimental-json-modules' node ./scripts/postbuild.mjs
# After (Astro)
astro build
AI-Powered Migration with Claude Code
I will be honest: I did not hand-write every line of this migration. Claude Code did a lot of the heavy lifting — rewriting the file structure, converting React components to Svelte and Astro, setting up the new routing, and wiring up Content Collections.
What made this work well was not just the code generation but the tooling around it:
- The astro-docs MCP server gave Claude access to up-to-date Astro documentation during development, so it was working from current API references instead of stale training data.
- Claude in Chrome integration helped with visual testing — verifying dark mode, checking layout issues, and debugging interactive components directly in the browser.
- The frontend-design skill helped with UI component implementation when I needed design-quality output rather than just functional code.
That said, a lot of iterations and testing were still manual. I reviewed every piece of output, caught edge cases, and tested across themes and screen sizes. AI is a powerful collaborator, but it does not replace the person who knows what the site should look and feel like.
Some specific examples of AI-assisted work during this migration:
- A custom
rehype-lazy-imagesplugin that automatically addsloading="eager"to the first image in each post andloading="lazy"to all subsequent images - Compressing the avatar image from a 2.1 MB PNG to a 23 KB WebP — a 99% reduction
- Getting the search modal to properly follow the dark/light theme
- Wiring the code block copy button into MDX rendering via a custom
Pre.astrocomponent - Integrating Partytown to move analytics off the main thread
- Fixing font rendering inconsistencies between light and dark mode
Each of these was a back-and-forth process: Claude would generate a solution, I would test it, find issues, and iterate. The migration was genuinely faster because of it.
Challenges
No migration is without friction. Here are the practical gotchas I ran into:
The Vinext dead end. As mentioned above, I spent significant time on an alternative migration path before choosing Astro. The lesson: check your deployment target’s runtime constraints before committing to a framework migration. Cloudflare Workers’ security model blocks new Function(), which broke SSR in ways that took many commits to even partially work around. The full story is here.
Page transitions and theme flickering. Astro’s ClientRouter re-runs scripts on navigation. Without careful handling, this caused a dark mode flash — the page would briefly render in light mode before the theme script ran. Getting this right required understanding exactly when and how scripts execute during view transitions.
Sitemap breaking changes. Astro’s sitemap integration had breaking changes that needed workarounds. What should have been a straightforward configuration turned into a couple of commits to get right.
Cloudflare Workers configuration. Even with a fully static site, getting the Workers deployment configured correctly took two commits. The wrangler configuration needed to be set up to serve static assets properly.
Component parity. Rewriting React components to Svelte was mostly straightforward, but Svelte 5’s runes ($state, $derived, $effect) represent a fundamentally different reactivity model compared to React’s hooks. It is not a mechanical translation — you have to think about state differently.
The Results
The numbers speak for themselves:
- Dependencies: 54 → 28 (48% reduction)
- Dev dependencies: 18 → 2 (just TypeScript and Wrangler)
- Codebase: net -5,000 lines across 209 files changed
- Build command: from a multi-step
cross-envchain toastro build - Client-side JavaScript: near-zero — only interactive islands (search, theme toggle, mobile nav)
- Page transitions: smooth SPA-like navigation via the View Transitions API, without shipping a router
The site is simpler to maintain, faster to build, and serves lighter pages. The developer experience is better too — Astro’s file-based routing and Content Collections are more intuitive than the Next.js App Router and Contentlayer setup they replaced.
Should You Migrate?
It depends on what you are building.
Content-first sites — blogs, documentation, marketing pages — are where Astro excels. If most of your pages are static content with occasional interactivity, Astro’s islands architecture is a perfect fit. You get the performance of a static site generator with the component model of a modern framework.
Interactive applications — dashboards, SaaS products, apps with complex client-side state — should probably stick with Next.js, SvelteKit, or whatever full-stack framework fits their needs. Astro is not trying to be that.
The migration itself is non-trivial. Rewriting components, setting up new routing, migrating content — it takes real effort. But the result is a dramatically simpler codebase. If your Next.js blog feels like it is carrying more weight than it should, Astro is worth serious consideration.
Astro + Svelte + Cloudflare Workers is a great stack for content sites with light interactivity. Zero JS by default, compiled components where you need them, and global edge deployment on a generous free tier.
If you are curious what the old site looked like, you can still see the old Next.js version. If you have questions about the migration or want to share your own experience, feel free to reach out.