Back to blog
Performance

Why I chose Astro over Next.js

By Flávio Emanuel · · 8 min read

The moment I stopped

I was delivering a landing page for a law firm. Five pages, zero interactivity, just text, photos, and a WhatsApp link. I opened DevTools to check the bundle: 287 KB of JavaScript. To render static text.

Next.js loads the entire React runtime, hydrates the whole page, and ships the framework to the client even when there isn’t a single useState in the project. For webapps, that makes sense. For a 5-page site, it’s dead weight.

It wasn’t an emotional decision. It was a number in DevTools that didn’t add up. I started testing Astro that same week.

What Next.js does under the hood

To understand why I switched, I need to explain what Next.js does that you didn’t ask for.

When you create a page in Next.js, even if it’s pure static HTML, the framework generates a JavaScript bundle that includes the React runtime, the Next router, and hydration code. Hydration is the process of “reviving” static HTML in the browser, attaching event listeners and React state. On a page with no state, no events, and no interaction, that’s wasted work.

The minimum bundle for a Next.js project sits between 70 KB and 90 KB compressed. Sounds small, but it adds up with every page, every component, every lib you import. On a business site with 5-8 pages, you easily hit 200-300 KB of JS that the visitor downloads for no reason.

And there’s the hidden cost: hydration blocks the main thread. While React is attaching listeners to static elements, the browser is busy. This directly impacts INP (Interaction to Next Paint), one of the metrics Google uses for ranking.

What changed in practice

Client JS: from 287 KB to 0

Astro generates static HTML at build time. The browser receives pure HTML. No React, no hydration, no bundle. The same law firm site in Astro: zero bytes of JavaScript. Literally.

I measured across all my projects:

  • GPM2 (tax consulting site): if it were Next.js, I’d estimate ~220 KB of JS. In Astro: 0 KB
  • Family Pilates (LP): 0 KB of JS, Lighthouse 97
  • Carrega Rapido (dense LP with many sections and technical data): 0 KB of JS, loads in 0.9s
  • Tok Final (corporate site for construction companies): 0 KB of JS, works even on 3G at a construction site
  • Soline (renewable energy with lots of field photos): 0 KB of JS, Lighthouse 95+ even with high image volume

That’s 6 projects in production. None of them ship JavaScript to the client, and all score above 95 on Lighthouse without manual optimization.

Lighthouse without trying

With Next.js, I spent time optimizing: manual lazy loading, dynamic imports, bundle splitting, preconnect for external fonts, selective prefetch. It was a 15-item checklist to hit a Lighthouse score of 90.

With Astro, the architecture delivers 95+ by default. The time I used to spend optimizing the framework now goes into content and site design. The tool stopped getting in the way.

Component Islands when you need them

Zero JS doesn’t mean zero interactivity. Family Pilates has a testimonial carousel. Only that component loads JavaScript. The rest of the page (hero, services, about, CTA) stays static.

That’s the Islands Architecture concept: the page is an ocean of static HTML with islands of interactivity. Each island hydrates independently. The carousel loads 12 KB of JS. Everything else: zero.

In Next.js, if one component needs JS, the entire framework comes along. There’s no way to isolate it. In Astro, the client:visible directive makes the component load JS only when it enters the viewport. Before that, it’s inert HTML.

Build time

Next.js on an 8-page site: 15-30 seconds to build. Astro: 3-8 seconds. Seems irrelevant, but when you’re iterating on design and building every 2 minutes, the difference adds up. By the end of a workday, that’s 20-30 fewer minutes staring at the terminal.

There’s another point few people mention: Next.js builds get slower as the project grows because it needs to process the entire React dependency graph, even for pages that don’t use any dynamic components. Astro treats each page as independent — building a 200-line page takes the same time whether the project has 5 or 50 pages.

DX (Developer Experience)

Astro uses its own syntax (.astro) that feels like a mix of HTML and JSX. The frontmatter sits at the top of the file between ---, and the template goes below. It’s simpler than the Next.js pages/components model because there are no server components, client components, API routes, or middleware — just static files with templates.

There’s a trade-off: if you’re coming from React, it takes 1-2 days to get used to the syntax. But once it clicks, you’re more productive for static content.

Another practical advantage: Astro accepts components from any framework. If you have a ready-made React component, you can use it inside an .astro file without rewriting anything. Same goes for Svelte, Vue, or Solid. This means migrating from Next.js to Astro doesn’t require throwing away everything you already have — you can bring isolated components and use them with client:load or client:visible as needed.

Where Astro doesn’t work

I don’t use Astro for webapps. I need to be clear about this because the takeaway isn’t “Astro is better” — it’s “Astro is better for a specific type of project.”

AutoPars (marketplace with 3 panels, authentication, shared state, real-time updates, 5 integrations) is React with TypeScript. It wouldn’t make sense in Astro. The system has a shopping cart with persistent state, an admin dashboard with real-time data, and forms with complex validation. All of that needs client-side JavaScript.

Mariah (order management with a financial dashboard) is also React. A dashboard with filters, charts, and real-time updates doesn’t work as static HTML.

FitPlan (gym platform with 6 panels) is React. A system with interactive workouts, videos, notifications, and physical assessments needs a full SPA.

If the project has login, state, complex forms, and internal navigation, React is the right tool. Astro wasn’t made for that and doesn’t try to be.

The decision framework I use today

Before starting any project, I ask three questions:

1. Does the user log in? If yes, React. If no, Astro.

2. Does the content change after the page loads? If data updates in real-time, if there’s a form with state, if there’s interaction beyond scrolling and clicking links — React. If the page is the same for everyone who visits — Astro.

3. Is SEO a priority? If yes, Astro wins by default. React needs SSR or SSG configured to be indexed properly. Astro is built that way from the start. That’s why AutoPars needed a separate LP in Astro as an SEO layer — the React SPA doesn’t index well on its own.

If the project needs both, I build two separate projects. Each one on the right stack. The complication was using Next.js for everything out of habit.

Numbers at a glance

MetricNext.js (static site)Astro
Client JS180-300 KB0 KB
Lighthouse (no tweaking)70-8595+
Build time15-30s3-8s
Config complexityMediumLow
HydrationMandatoryOptional (Islands)
Native SEONeeds SSR/SSGYes

What about the Next.js App Router?

Since the App Router (Next.js 13+), Next has improved for static content with React Server Components. But it still ships the React runtime to the client when any component uses 'use client'. And in practice, almost every Next project ends up with at least one 'use client' — an interactive header, a form, a toggle.

Astro solves this more cleanly: zero JS is the default, and you opt into JS component by component. It’s an inverted philosophy.

In practice, I tested migrating a 6-page project from the App Router to Astro. The bundle dropped from 112 KB to 0. Lighthouse went from 88 to 98. And build time was cut in half. The App Router made Next.js better for static content, but it still can’t compete with a tool that was born for it.

Who this post is for

If you’re a dev still using Next.js for landing pages, open DevTools right now and check the bundle. Filter by JS in the Network tab. The number will bother you.

If you’re a business owner and the dev delivered a corporate site that takes 3 seconds to load, ask them: how much JavaScript is being shipped to the browser? If the answer is “but we need React for…”, ask what for exactly. If it’s to render text and images, you don’t need it.

The best JavaScript is the kind that doesn’t need to exist. The right question was never “which framework to use.” The question is “does this project need a JavaScript framework on the client?” For most corporate sites and landing pages, the answer is no. Astro just makes that answer easier to implement.

Next step

Need a dev who truly delivers?

Whether it's a one-time project, team reinforcement, or a long-term partnership. Let's talk.

Chat on WhatsApp

I reply within 2 hours during business hours.