Astro vs Next.js for SEOs: Which Framework Won't Sabotage Your Rankings
Next.js powers some of the best sites on the web. It also powers some of the most frustrating SEO debugging sessions I’ve had. After building and maintaining production sites on both Next.js and Astro, I have opinions about which framework makes SEO easier and which one makes it harder. This is that comparison, from an SEO practitioner’s perspective.
The short version: if your site is mostly content, Astro removes entire categories of SEO problems that Next.js creates. If your site is a web application that happens to need SEO, Next.js is still the right tool. The distinction matters.
The Next.js Rendering Problem
Next.js has five rendering modes: SSG (Static Site Generation), SSR (Server-Side Rendering), ISR (Incremental Static Regeneration), RSC (React Server Components), and client-side hydration. On top of that, Vercel adds three caching layers: Full Route Cache, Data Cache, and CDN edge cache.
Each of these solves a real problem. Together, they create a system where content delivery depends on which cache layer responds, whether ISR has revalidated, whether hydration has completed, and whether the client-side router has taken over navigation. When any of these layers misbehave, the result is often a page that returns HTTP 200 but serves wrong or missing content. Search engines index these pages. You don’t find out until rankings drop.
Here are bugs I’ve encountered on production Next.js sites.
Empty 200 Pages from ISR
ISR revalidates pages in the background. During revalidation, the old cached version is served. If revalidation fails silently (API timeout, build error), the stale version persists indefinitely. Worse, I’ve seen cases where ISR returns an empty shell with a 200 status. Google indexes the empty page, the original content disappears from search results, and the only symptom is a traffic drop days later.
The fix involves monitoring revalidation failures, but Next.js doesn’t expose these failures by default. You need custom error tracking on revalidation callbacks, which most teams don’t set up.
_rsc Parameter Pollution
Next.js appends ?_rsc={hash} to URLs for React Server Component payloads. These URLs return text/x-component content type, not HTML. On one production site, Screaming Frog found roughly 45,000 URLs with _rsc parameters against only 1,500 actual pages. That’s a 30:1 ratio of junk URLs to real content.
These URLs waste crawl budget and can confuse search engines if they’re not blocked. The fix is adding Disallow: /*?_rsc= to robots.txt, but you have to know the problem exists first.
Client Components Kill Link Crawlability
Google treats links inside 'use client' components as plain text. If your navigation, footer links, or internal linking components are client components, Googlebot may not follow those links during the initial crawl. We discovered this on a production site via Search Console URL Inspection, where pages that should have been crawled through navigation links were showing as “Discovered - currently not indexed.”
The rule is simple: every component that renders <a> tags must be a server component. But Next.js makes it easy to accidentally promote components to client components through import chains. One 'use client' at the top of a shared utility file can cascade through your entire component tree.
loading.tsx Creates JS-Dependent Content
Adding a loading.tsx file to your route creates an implicit Suspense boundary. This means all content in that route streams behind <div hidden>, invisible until JavaScript executes. Googlebot executes JavaScript, but with delays. Other crawlers (Bing, Yandex) may not render JavaScript at all.
This pattern also causes duplicate <title> tags (the streaming response sends the title twice) and missing canonicals during the streaming phase. If you’re using loading.tsx on SEO-critical pages, you’re relying on every crawler to fully render JavaScript before indexing.
Soft Navigation Loses Meta Tags
Next.js App Router uses client-side navigation (soft navigation) between pages by default. When a user clicks a link, the client-side router fetches the new page’s content and updates the DOM without a full page load. This is fast for users but can cause issues with meta tags.
If your Open Graph tags, canonical URLs, or structured data are set via generateMetadata(), they update correctly on soft navigation. But any meta tags set outside of generateMetadata() (via custom <Head> components or third-party scripts) may not update. The page looks correct to users, but sharing the URL on social media or having it crawled mid-session can produce wrong metadata.
The Astro Alternative
Astro has one default rendering mode: static HTML at build time. No JavaScript ships unless you explicitly request it with a client:* directive on a specific component.
The HTML that exists at build time is the HTML that gets served. There is no ISR, no hydration race, no cache invalidation timing, no revalidation webhooks to secure. If the MDX file has content, the built HTML has content. If the build succeeds, every page is correct.
What Fails in Astro
Build failures. That’s essentially it. If your content has a syntax error or your schema validation fails, the build stops and deployment is blocked. This is loud and obvious, the opposite of Next.js’s silent 200-with-wrong-content failures.
The other failure mode is content staleness: if you commit new content but don’t trigger a rebuild, the site serves the previous version. This is solved by a deploy hook in your CI pipeline. For sites using GitHub Actions, it’s a one-line webhook call after content commits.
Zero JavaScript by Default
A typical Astro content page sends HTML and CSS. Nothing else. No React runtime (45KB+ gzipped), no hydration logic, no client-side router. The browser parses HTML and renders. That’s it.
When you do need interactivity, Astro’s islands architecture lets you add React, Vue, Svelte, or any other framework to specific components. A search modal can be a React component with client:visible (loaded only when scrolled into view). The other 95% of the page remains static HTML.
This means you get React where you need it and zero JavaScript where you don’t. The framework tax only applies to interactive widgets, not to your entire content library.
Core Web Vitals: Head-to-Head
Performance isn’t just about speed. Core Web Vitals directly affect rankings. Here’s how the two frameworks compare on the metrics Google measures.
Largest Contentful Paint (LCP)
Astro wins by default. With no JavaScript blocking the render, the browser can paint content immediately after receiving HTML. There’s no hydration phase where the framework takes over the DOM.
Next.js can achieve good LCP with static export and proper image optimization, but hydration adds a processing step. If your LCP element is inside a component that hydrates, the paint is delayed until React reconciles the server-rendered HTML with the client-side virtual DOM.
Interaction to Next Paint (INP)
Astro pages with no JavaScript have an INP of zero. There’s nothing to process on interaction except native browser behavior.
Next.js ships event handlers, state management, and the React reconciliation cycle. Even on static content pages, the hydrated React runtime processes every click, scroll, and keyboard event through React’s synthetic event system.
Cumulative Layout Shift (CLS)
Both frameworks can achieve zero CLS with proper image sizing and font loading. But Next.js has an additional CLS risk: hydration. When React hydrates a server-rendered page, it can cause layout shifts if the client-side render differs from the server render. This is rare with careful coding but happens in practice, especially with conditional rendering based on browser APIs like window.innerWidth.
Astro doesn’t hydrate content components, so this category of CLS doesn’t exist.
Real Numbers
This site (technicalseonews.com) runs on Astro. Lighthouse scores: Performance 100, LCP 1.5s, Total Blocking Time 0ms, CLS 0. These numbers require zero optimization effort. The framework defaults produce them.
Achieving similar scores on Next.js is possible but requires work: optimizing the React bundle, code-splitting aggressively, lazy-loading non-critical components, and avoiding hydration on content pages. You’re spending engineering time to reach numbers that Astro gives you for free.
Next.js Caching: Six Layers of Confusion
Next.js has a well-documented caching problem. The framework itself has four cache layers, and Vercel adds two more. Understanding which layer is serving (or blocking) your content requires expertise that most teams don’t have.
Layer 1: Request Memoization. Deduplicates identical fetch calls within a single render. Mostly harmless, rarely causes SEO issues.
Layer 2: Data Cache. Caches the results of fetch() calls across requests. Persists across deployments unless explicitly invalidated. This is where stale data lives longest.
Layer 3: Full Route Cache. Caches the rendered HTML and RSC payload at build time for static routes. ISR pages get re-cached after revalidation.
Layer 4: Router Cache. Client-side cache that stores previously visited pages for 30 seconds (dynamic) or 5 minutes (static). Users see stale content during this window.
Layer 5: Vercel Edge Cache. CDN-level caching with edge nodes worldwide. Invalidation propagates asynchronously, so different edge nodes can serve different versions.
Layer 6: Browser Cache. Standard HTTP caching based on headers. Interacts with all other layers.
When a page serves stale content, the debugging question is: which of these six layers is responsible? The answer is often “multiple layers, in combination.” This is not a theoretical problem. Teams building content sites on Next.js spend real engineering hours on cache debugging that produces no user-visible value.
Astro’s caching model: the CDN serves static files. Set your cache headers. Done.
Should You Migrate?
Not every Next.js site should move to Astro. The decision depends on what your site does and what problems you’re experiencing.
Migrate If
Your site is primarily content (blog, docs, news, marketing pages) with limited interactivity. You’ve experienced ISR staleness, hydration bugs, or cache confusion. Your team spends time debugging rendering issues instead of building features. Your Core Web Vitals need work and you’d rather fix the root cause than add optimization patches.
The migration cost is proportional to your interactive surface area. If you have 3 interactive components across 500 content pages, the migration is straightforward: rebuild the content templates in Astro, port the 3 components as islands. Most of your content (MDX or Markdown) transfers with minimal changes.
Stay on Next.js If
Your site is a web application with complex state management, real-time data, user authentication, and dynamic UI that changes per request. Dashboard-style products, e-commerce with personalized pricing, SaaS platforms with user-specific views. These need Next.js’s server-side rendering and API routes.
Also stay if your team has deep Next.js expertise and hasn’t experienced the bugs described above. The problems I’ve outlined are real but not universal. Teams that understand Next.js’s rendering modes and caching layers can avoid them. The question is whether the ongoing vigilance is worth the cost.
The Hybrid Option
Some teams keep Next.js for their web application and use Astro for their marketing site, docs, or blog. This gives each site the framework that fits its content model. The marketing site gets Astro’s simplicity and performance. The application gets Next.js’s dynamic capabilities. Two deploys, but each one is simpler than a combined deployment would be.
What Astro Gets Wrong (Or At Least Harder)
Astro isn’t perfect. Here are the genuine trade-offs.
No Incremental Updates
Every content change requires a full rebuild. For a 500-page site, this takes 10-30 seconds. For a 50,000-page site, build times grow and become a bottleneck. If you need content to appear within seconds of publishing (breaking news, stock prices, live scores), Astro’s rebuild cycle adds latency that ISR can avoid.
For most content sites, a 30-second deploy pipeline is fast enough. But if you need sub-second content freshness, Astro isn’t the right choice.
Smaller Community and Fewer Packages
Next.js has more tutorials, Stack Overflow answers, and third-party packages. If you hit an obscure bug, there’s more community help available. Astro’s community is growing quickly (especially after the Cloudflare acquisition in January 2026) but it’s still smaller in absolute terms.
For a content site, this rarely matters. The core features you need (MDX, Tailwind, search, RSS, sitemap, syntax highlighting) all have first-class Astro support.
No API Routes in Static Mode
Astro can serve API endpoints, but only in SSR mode. In static mode (the default and recommended mode for content sites), you can’t have server-side API routes. Contact forms, webhooks, and other server-side logic need external services (Supabase, Cloudflare Workers, etc.) or a separate API.
Next.js API routes are convenient for keeping everything in one framework. Astro’s approach separates concerns more strictly, which is architecturally cleaner but requires more infrastructure decisions.
Font Loading
There’s no equivalent to next/font’s automatic font optimization. You self-host fonts with preload links and font-display: swap, which is what next/font does under the hood, but you manage it yourself. It’s a few lines of HTML, not a real burden, but it’s less automatic.
Side-by-Side Comparison
| Dimension | Next.js | Astro |
|---|---|---|
| Default rendering | SSR + client hydration | Static HTML, zero JS |
| JavaScript shipped | React runtime + hydration (~45KB+ gzipped) | Zero by default |
| Rendering modes | 5 (SSG, SSR, ISR, RSC, client) | 1 (static) + optional SSR |
| Cache layers | 6 (request, data, route, router, edge, browser) | 1 (CDN) |
| Interactive components | React (always loaded) | Islands (React/Vue/Svelte, loaded on demand) |
| Content updates | ISR (background revalidation) | Full rebuild + deploy |
| Build time (500 pages) | 15-45 seconds | 5-15 seconds |
| Lighthouse Performance (content page) | 85-100 (depends on config) | 95-100 (default) |
| SEO debugging complexity | High (which cache layer? which rendering mode?) | Low (check the HTML file) |
| MDX support | Via @next/mdx or contentlayer | Built-in Content Collections + Zod |
| Sitemap | Custom generation or next-sitemap | @astrojs/sitemap (official) |
| RSS | Custom generation | @astrojs/rss (official) |
| Structured data | Manual JSON-LD | Manual JSON-LD (same) |
| robots.txt | Custom API route or static file | Static file or dynamic endpoint |
Recommendations
If you’re starting a new content site: Use Astro. The rendering simplicity, zero-JS default, and built-in content tooling (Collections, MDX, Zod schemas) are purpose-built for content. You’ll spend your time writing content instead of debugging caching.
If you’re maintaining a Next.js content site with SEO issues: Audit your rendering modes first. Check for _rsc parameter pollution, test pages with JavaScript disabled, and verify that your ISR revalidation is working. Many Next.js SEO issues can be fixed without migrating, they just require understanding the rendering pipeline.
If you’re maintaining a Next.js content site without SEO issues: Keep it. Migration has a cost, and if your current setup works, the rendering reliability gains from Astro may not justify the switch.
If you’re building a web application that needs SEO: Use Next.js (or Remix, or SvelteKit). Astro is not a web application framework. Its strength is content, not complex interactive UIs.
The framework choice matters less than understanding the framework you choose. Both Next.js and Astro can produce excellent SEO outcomes. The difference is how much effort and expertise each one demands to get there. For content sites, Astro demands less.