Astro vs Next.js 16: Choosing the Right Framework
We built our documentation site in Next.js because that's what we knew. Then we discovered Astro shipped our 200-page docs with 12KB of JavaScript. The Next.js version used 180KB. For content-focused sites, Astro's architecture fundamentally changes the equation. But for our product dashboard, Next.js remains unbeatable. Here's how to choose.
The Fundamental Difference
Next.js is a React framework. Every page is a React component that hydrates on the client. Even static pages ship the React runtime.
Astro is a content-focused framework. It renders components to HTML and ships zero JavaScript by default. Interactive islands load only when needed.
---
// Astro component - renders to HTML, no JS
const posts = await fetchPosts();
---
<ul>
{posts.map(post => (
<li><a href={`/blog/${post.slug}`}>{post.title}</a></li>
))}
</ul>// Next.js component - includes React runtime
export default async function PostList() {
const posts = await fetchPosts();
return (
<ul>
{posts.map(post => (
<li><Link href={`/blog/${post.slug}`}>{post.title}</Link></li>
))}
</ul>
);
}Same output HTML. Very different JavaScript payloads.
JavaScript Payload Comparison
I built the same marketing site in both frameworks:
| Metric | Astro | Next.js 16 | |--------|-------|------------| | Initial JS | 0KB | 85KB | | With one interactive component | 12KB | 92KB | | Full page with nav, forms, analytics | 45KB | 156KB | | Lighthouse Performance | 100 | 89 |
Astro's advantage is absolute for static content. When you add interactivity, the gap narrows but remains significant.
Astro's Island Architecture
Astro's killer feature is partial hydration. You choose which components become interactive:
---
import Header from '../components/Header.astro';
import Hero from '../components/Hero.astro';
import SearchWidget from '../components/SearchWidget.tsx';
import Newsletter from '../components/Newsletter.tsx';
---
<Header /> <!-- Static HTML, no JS -->
<Hero /> <!-- Static HTML, no JS -->
<!-- Only these components ship JavaScript -->
<SearchWidget client:load /> <!-- Loads immediately -->
<Newsletter client:visible /> <!-- Loads when scrolled into view -->The client:* directives control when JavaScript loads:
client:load- Immediate hydrationclient:idle- Hydrate when browser is idleclient:visible- Hydrate when component enters viewportclient:media- Hydrate when media query matchesclient:only- Skip SSR, render client-only
This granular control is impossible in Next.js, where the React runtime loads for the entire page.
Next.js 16's Strengths
Next.js excels where Astro doesn't:
Full-Stack Applications
Next.js integrates server actions, route handlers, and database access seamlessly:
// app/products/[id]/page.tsx
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
async function addToCart(formData: FormData) {
'use server';
const productId = formData.get('productId');
await db.cart.add({ productId, userId: getCurrentUser() });
revalidatePath('/cart');
}
export default async function ProductPage({ params }) {
const product = await db.products.find(params.id);
return (
<form action={addToCart}>
<input type="hidden" name="productId" value={product.id} />
<button type="submit">Add to Cart</button>
</form>
);
}Astro can do this with API endpoints, but it's not as integrated.
Complex State Management
Applications with shared state across many components favor React's ecosystem:
// Complex dashboard with global state
function Dashboard() {
const { user } = useAuth();
const { theme } = useTheme();
const { notifications } = useNotifications();
const { cart } = useCart();
return (
<DashboardLayout>
<Sidebar notifications={notifications} />
<MainContent user={user} />
<CartWidget cart={cart} />
</DashboardLayout>
);
}Every component shares context. In Astro, you'd need to coordinate state across isolated islands.
Real-Time Features
WebSocket connections, live updates, and real-time collaboration work naturally in Next.js:
'use client';
function LiveComments({ postId }) {
const [comments, setComments] = useState([]);
useEffect(() => {
const ws = new WebSocket(`wss://api.example.com/comments/${postId}`);
ws.onmessage = (event) => {
setComments(prev => [...prev, JSON.parse(event.data)]);
};
return () => ws.close();
}, [postId]);
return <CommentList comments={comments} />;
}Astro can embed React components for this, but you're essentially using React anyway.
When to Choose Astro
Content websites: Blogs, documentation, marketing pages. The zero-JS default means optimal performance without effort.
Multi-framework teams: Astro supports React, Vue, Svelte, and Solid components in the same project. Teams with mixed expertise can contribute.
Performance-critical landing pages: E-commerce landing pages where every kilobyte affects conversion rates.
Static-first with occasional interactivity: Sites that are mostly reading with isolated interactive features (search, forms, comments).
When to Choose Next.js 16
Full-stack applications: Apps with authentication, database operations, and server-side logic.
Complex React applications: Dashboards, admin panels, SaaS products with intricate state.
React ecosystem dependency: Projects using React-specific libraries (React Query, React Hook Form, React Native Web).
Team familiarity: Teams already proficient in React gain velocity from staying in the ecosystem.
Vercel deployment: Tight integration with Vercel's infrastructure (Edge Functions, KV, Analytics).
Hybrid Approach
You don't have to choose exclusively. Some teams use both:
Astro for marketing site → Best performance for content pages Next.js for application → Best DX for complex features
marketing.example.com → Astro
app.example.com → Next.js
docs.example.com → AstroOr use Astro with React islands for the marketing site, then link to a Next.js app:
---
// Astro marketing page with React pricing calculator
import PricingCalculator from '../components/PricingCalculator.tsx';
---
<h1>Our Pricing</h1>
<p>Calculate your costs:</p>
<PricingCalculator client:visible />
<a href="https://app.example.com/signup">Get Started</a>Migration Considerations
Astro to Next.js
If your Astro site grows more interactive:
- React components already work in Astro
- Extract shared components to a package
- Build the Next.js app alongside
- Migrate pages incrementally
Next.js to Astro
For content-heavy Next.js sites that don't need React:
- Astro supports MDX content natively
- Convert pages to Astro components
- Keep interactive parts as React islands
- Expect significant performance gains
Performance in Practice
Real-world metrics from sites I've worked on:
Documentation Site (200 pages)
- Next.js: LCP 1.8s, TTI 2.4s, 92 Lighthouse
- Astro: LCP 0.6s, TTI 0.8s, 100 Lighthouse
E-commerce Landing Page
- Next.js: LCP 1.2s, TTI 1.8s
- Astro: LCP 0.5s, TTI 0.7s
- Conversion difference: +8% for Astro version
SaaS Dashboard
- Next.js: LCP 1.4s, TTI 2.1s
- Astro attempt: Impractical due to state complexity
- Stayed with Next.js
The Decision Framework
Ask these questions:
- Is the site primarily content? → Lean Astro
- Does every page need rich interactivity? → Lean Next.js
- Is React your team's primary skill? → Lean Next.js
- Is JavaScript payload size critical? → Lean Astro
- Do you need tight Vercel integration? → Lean Next.js
- Is the site multi-framework? → Lean Astro
Neither framework is universally better. They optimize for different use cases. Choose based on your specific needs, not framework popularity.