DEV Community

Cover image for Why I Ditched Next.js and Rebuilt My Site with Astro
Waseem raja Shaik
Waseem raja Shaik

Posted on • Originally published at waseemrajashaik.me

Why I Ditched Next.js and Rebuilt My Site with Astro

Sometimes the best code is the code you don't ship to the browser.

// package.json diff
- "next": "15.3.5",
- "react": "19.0.0",
- "framer-motion": "12.23.0",
- "gsap": "3.13.0",
+ "astro": "6.x",
+ // that's... kind of it
Enter fullscreen mode Exit fullscreen mode

Let me be clear upfront: I still love Next.js. I spent months building my v1 portfolio with it. React 19, Tailwind v4, Framer Motion, GSAP, dual design modes with smooth morphing animations, the whole nine yards. It was a flex. It looked great. I was proud of it.

But then I asked myself a simple question: "What am I actually building here?"

The Realization

My v1 site was a portfolio. It had an about section, experience timeline, skills grid, project cards, a writing section, and social links, all on one page with fancy animations. The blog was there, but it was secondary.

When I decided to pivot to weekly blogging, I looked at my Next.js setup and thought:

Framework JS shipped to browser: ~180KB
React runtime: ~45KB
Framer Motion: ~60KB
GSAP: ~30KB
Content that actually changes: some markdown text

🤔 Something doesn't add up.
Enter fullscreen mode Exit fullscreen mode

I was shipping a full React runtime, animation libraries, and client-side hydration... to render static text that never changes. Every single blog post was being hydrated on the client, running JavaScript, setting up event listeners, for content that's literally just paragraphs and code blocks.

That's when it hit me: I was using a chainsaw to butter toast.

Why Not Just Keep Next.js?

Fair question. Next.js can do static sites. It has SSG, ISR, the whole deal. But here's what bugged me:

1. The bundle tax

Even with static generation, Next.js ships React to the browser. Every page gets hydrated. For a blog post that's just text and code blocks, that's unnecessary JavaScript that the user's browser has to download, parse, and execute.

2. The complexity overhead

My v1 had content-collections, next-mdx-remote, react-markdown, @wooorm/starry-night for syntax highlighting, remark-gfm, rehype... the dependency tree for just rendering markdown was wild.

// v1: Dependencies just for blog content
"@content-collections/core": "0.10.0",
"@content-collections/mdx": "0.2.2",
"@content-collections/next": "0.2.6",
"next-mdx-remote": "5.0.0",
"react-markdown": "10.1.0",
"remark-gfm": "4.0.1",
"rehype": "13.0.2",
"@mdx-js/react": "3.1.0",
"@wooorm/starry-night": "3.8.0",
"hast-util-to-html": "9.0.5"
Enter fullscreen mode Exit fullscreen mode

3. The "everything is a component" trap

In React-land, even a simple blog post layout becomes a tree of components with state, effects, and client directives. My blog-post-layout.tsx was a client component importing react-markdown with custom renderers, wrapped in motion divs. For what? To display an article.

Enter Astro

I'd been hearing about Astro for a while. The pitch was simple: ship zero JavaScript by default. Only hydrate what actually needs interactivity. For a blog, that's... almost nothing.

Here's what sold me:

1. Zero JS by default

Astro renders everything to HTML at build time. No React runtime shipped. No hydration. A blog post page literally sends HTML and CSS to the browser. That's it. The way the web was meant to work.

v1 (Next.js) blog post page: ~230KB JS
v2 (Astro) blog post page: ~0KB JS (just HTML + CSS)
Enter fullscreen mode Exit fullscreen mode

Yes, zero JavaScript on a blog post page. The only JS on the entire site is a tiny inline script for the dark mode toggle.

2. Built-in content collections

Astro has content collections as a first-class feature. No third-party packages needed. Define a schema, drop MDX files in a folder, and you're done.

// That's the entire content setup
const blog = defineCollection({
  loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    date: z.coerce.date(),
    tags: z.array(z.string()),
    published: z.boolean().default(false),
  }),
});
Enter fullscreen mode Exit fullscreen mode

Compare that to the three packages and custom transform functions I needed in Next.js.

3. Shiki built-in

Syntax highlighting just works. No installing starry-night or prism or configuring rehype plugins. Astro uses Shiki out of the box with dual theme support:

// astro.config.mjs
markdown: {
  shikiConfig: {
    themes: {
      light: 'github-light',
      dark: 'github-dark',
    },
  },
},
Enter fullscreen mode Exit fullscreen mode

That's it. Every code block in every blog post gets beautiful syntax highlighting with zero client-side JavaScript.

4. I can still use React (when I need it)

This was crucial. Astro's "islands" architecture means I can drop in a React component when I actually need client-side interactivity. Everything else is static HTML.

<!-- Only this component ships JS, everything else is static -->
<InteractiveWidget client:load />

<!-- The rest of the page is zero-JS HTML -->
Enter fullscreen mode Exit fullscreen mode

For my site, I ended up handling tag filtering and the sidebar toggle with small inline scripts instead of React. That's even less JavaScript shipped.

5. View Transitions API

Astro has built-in support for the View Transitions API. Smooth page-to-page animations with zero JavaScript bundle cost. No Framer Motion. No GSAP. Just native browser APIs.

The Migration

The actual migration was surprisingly smooth:

  • MDX posts: Copied all the files over. The frontmatter schema was almost identical, just had to remove some content-body --- separators that MDX interpreted as frontmatter delimiters.
  • Data: Extracted experience and skills data into simple TypeScript files.
  • Styling: Kept Tailwind v4, same CSS variables, same color scheme.
  • Dark mode: One inline script in the head. No next-themes package.

The whole thing took a day. The v1 had 15+ components and multiple npm packages for content. The v2 has Astro components (basically HTML with props) and barely any dependencies.

The Numbers

Next.js v1 Astro v2
Dependencies 30+ ~10
JS shipped (blog post) ~230KB ~0KB
Build time ~15s ~5s
Lighthouse Performance 85-90 99-100
Framework complexity High Low

What I Miss

I'll be honest, there are things I miss:

  • The morphing layout was genuinely cool. Switching between "original" and "swiss" design modes with smooth Framer Motion animations was a showpiece. But it was a portfolio flex, not a blogging feature.
  • React's component model is more powerful for complex UI. Astro components are simpler but that's by design.
  • The ecosystem: React has a package for everything. Astro's ecosystem is smaller (but growing fast).

The Takeaway

The best framework is the one that fits your use case. Next.js is incredible for web applications: dashboards, e-commerce, SaaS products, anything with lots of interactivity and dynamic data. I'd pick it again in a heartbeat for those.

But for a content-first blog where the primary job is serving static text with good typography and fast page loads? Astro is purpose-built for exactly that.

if (site === 'blog') {
  return 'astro';
} else if (site === 'app') {
  return 'nextjs';
} else {
  return 'it depends™';
}
Enter fullscreen mode Exit fullscreen mode

The v1 portfolio is still alive as an archive, a reminder that sometimes the best engineering decision is knowing when to use a simpler tool.

Now if you'll excuse me, I have a blog to write every week. And this time, the framework won't be in my way.

Top comments (2)

Collapse
 
adarsh_kant_ebb2fde1d0c6b profile image
Adarsh Kant

The Astro migration story resonates — framework bloat is real, especially when you don't need the full SPA complexity. The content-first approach with islands architecture makes so much more sense for most websites.

One thing I've noticed working on AnveVoice (we build voice AI that takes real DOM actions on websites): framework choice matters enormously for accessibility and DOM predictability. Static-first approaches like Astro produce cleaner, more semantic HTML which is actually easier for AI agents to parse and interact with. When your voice AI needs to click buttons and fill forms on a website, having predictable DOM structure is gold. Did you notice any accessibility improvements after the migration?

Collapse
 
rickcogley profile image
Rick Cogley

Are you hosting on Cloudflare and if so did you find any advantage to using Astro there?