Introduction: The Performance Cost of Modern JavaScript
In the early days of the web, websites were simple, static HTML pages that loaded instantly. However, as web applications grew in complexity, we adopted powerful Single Page Application (SPA) frameworks like React to build highly interactive, dynamic user interfaces.
To optimize these frameworks for search engines and initial load speeds, we transitioned to Server-Side Rendering (SSR) frameworks like Next.js. Next.js renders your React components on the server, sending pre-rendered HTML to the browser.
While Next.js is an exceptional framework for complex, highly dynamic web applications, it introduces a major performance penalty for content-focused websites (like blogs, documentation sites, e-commerce stores, and marketing portfolios): Hydration Overhead.
In 2026, Google's Core Web Vitals (specifically Interaction to Next Paint - INP, and Largest Contentful Paint - LCP) strictly penalize websites with heavy JavaScript execution.
This is where Astro shines.
Astro is a modern web framework designed specifically for content-focused websites. By adopting a revolutionary Islands Architecture, Astro delivers zero JavaScript by default, allowing you to build highly interactive websites that score a perfect 100/100 on Google PageSpeed Insights.
In this guide, we will walk through migrating a content-focused Next.js application to Astro. You will learn the architectural differences, compare performance benchmarks, and follow a step-by-step migration path to achieve 100% Core Web Vitals.
The Next.js Hydration Problem: Why PageSpeed Scores Drop
To understand why content-focused Next.js sites struggle to achieve perfect performance scores, we must look at how React's Hydration process works.
When a user visits a Next.js site:
- The server compiles the React components and sends static HTML to the browser. The user sees the page content almost instantly (fast First Contentful Paint - FCP).
- In the background, the browser downloads the entire React runtime library, along with all your page's component bundle files.
- Once downloaded, the browser executes the JavaScript, rebuilding the virtual DOM tree and attaching event listeners to the static HTML. This process is called Hydration.
During hydration, the browser's main execution thread is completely blocked. If a user attempts to click a button, open a menu, or scroll during this phase, the browser will freeze, resulting in a poor Interaction to Next Paint (INP) score.
For a content-focused page that is mostly static text and images (like a blog post), downloading and executing megabytes of React JavaScript simply to render a static page is a massive waste of resources.
Astro's Islands Architecture: Zero-JS by Default
Astro solves the hydration problem by pioneering the Islands Architecture.
Instead of building your entire website as a single, monolithic JavaScript application, Astro compiles your pages into 100% static, raw HTML and CSS at build time. All JavaScript is completely stripped away.
If your page requires interactive elements (like a search bar, an image carousel, or a mobile menu toggle), you can embed them as isolated Islands of Interactivity:
- Static HTML: The header, footer, text paragraphs, and layout remain static, requiring zero client-side JavaScript.
- Interactive Island: Only the specific interactive component (e.g., a React, Svelte, or Vue component) is hydrated on the client.
Furthermore, Astro allows you to control *when* and *how* these islands hydrate using Client Directives:
<Component client:load />: Hydrates the component immediately on page load.<Component client:idle />: Hydrates the component only when the browser's main thread is idle.<Component client:visible />: Hydrates the component only when it scrolls into the user's viewport (perfect for heavy, below-the-fold components).
Performance Benchmarks: Next.js vs. Astro
We migrated a highly optimized Next.js technical blog (featuring Tailwind CSS, syntax highlighting, and a search bar) to Astro. Here are the performance metrics measured on mobile devices:
| Metric | Next.js (v15 App Router) | Astro (v4.x) |
|---|---|---|
| JavaScript Shipped | ~240kb (React + Router + Page) | ~12kb (Only for Search Island) |
| LCP (Largest Contentful Paint) | ~1.8 seconds | ~0.6 seconds (Instant) |
| INP (Interaction to Next Paint) | ~180ms (Main thread blocked) | ~15ms (Zero thread blocking) |
| PageSpeed Mobile Score | 74 / 100 | 100 / 100 (Perfect) |
Step-by-Step Migration Guide
Let's execute a structured migration from Next.js to Astro.
Step 1: Bootstrap a New Astro Project
Initialize a clean Astro project in a parallel directory:
npm create astro@latest
Step 2: Install the React Integration
Since Astro is framework-agnostic, you must install the React integration to reuse your existing React components:
npx astro add react
Step 3: Migrate Layouts and Routing
Astro uses a file-based routing system inside the src/pages/ directory, identical to Next.js Pages Router:
- Move your Next.js
app/layout.tsxwrapper code into an Astro Layout component atsrc/layouts/Layout.astro. - Move your Next.js page files (e.g.,
app/page.tsx,app/blog/[slug]/page.tsx) into Astro page files (src/pages/index.astro,src/pages/blog/[slug].astro).
Code Comparison: Next.js vs. Astro Pages
Let's compare how a dynamic blog post page is written in both frameworks.
Next.js Implementation (Server Component)
// app/blog/[slug]/page.tsx
import { getPostBySlug } from '@/lib/api';
export default async function Post({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
return (
<article class="prose">
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
Astro Implementation
Astro separates server-side data fetching from the HTML template using a Component Script block enclosed in triple-dashes (---):
---
// src/pages/blog/[slug].astro
import Layout from '../../layouts/Layout.astro';
import { getPostBySlug } from '../../lib/api';
const { slug } = Astro.params;
const post = await getPostBySlug(slug);
---
<Layout title={post.title}>
<article class="prose">
<h1>{post.title}</h1>
<Fragment set:html={post.content} />
</article>
</Layout>
Best Practices for Astro Performance
To guarantee a perfect 100/100 Core Web Vitals score after migrating to Astro, implement these guidelines:
- Use Astro's Native Image Component: Never use standard
<img>tags. Use Astro's<Image />component, which automatically optimizes formats (WebP/AVIF), resizes images, and prevents Layout Shifts (CLS). - Limit Client Directives: Only add
client:*directives to components that absolutely require client-side interactivity. If a component is purely visual, render it as static HTML. - Prefetch Routes: Enable Astro's native prefetching to download page data in the background when a user hovers over a link, making page transitions feel instantaneous:
<a href="/about" data-astro-prefetch>About Us</a>
Conclusion: The Right Tool for the Job
Next.js remains an incredible framework for highly dynamic, authenticated web applications (like SaaS dashboards, social networks, or real-time portals).
However, for content-focused websites where performance, SEO, and Core Web Vitals are paramount, Astro is the undisputed winner.
By migrating your content layer to Astro's Islands Architecture, you can eliminate hydration overhead, slash your JavaScript bundle sizes to near-zero, and deliver a lightning-fast browsing experience that ranks perfectly on search engines.
To test your newly migrated Astro site's performance and response headers, launch our interactive HTTP Status Lookup Tool, or read our guide on Tailwind v4 Layout Fixes to optimize your CSS styles.
















