The short answer
Migrate from Next.js to Astro when the site is mostly content (marketing pages, a blog, docs) and every page still ships a React app to the browser. In the HTTP Archive's September 2026 data, the median Next.js site loads 822 KB of JavaScript on mobile against 409 KB for Astro, and 35.7% of Next.js sites pass Core Web Vitals on phones against 71.1% of Astro sites.
You keep your React components: Astro renders them as islands that load only where you ask. The real work is elsewhere: routes and data fetching, images, metadata, and above all your URLs, because Next.js and Astro disagree about trailing slashes and redirect codes by default. Don't migrate a signed-in product with dashboards and heavy client state; that is what Next.js is for.
When the migration pays
The case for moving is weight. A content page needs HTML, CSS and a few small scripts; a Next.js page also carries React, the client router and the payload React uses to hydrate. The HTTP Archive's technology report, which combines Chrome UX Report field data with its own monthly crawl, shows how that adds up across the web (September 2026, mobile, all countries):
| Mobile, September 2026 | Next.js | Astro | All sites |
|---|---|---|---|
| Sites measured | 395,345 | 47,277 | 8,569,446 |
| Pass all Core Web Vitals | 35.7% | 71.1% | 52.8% |
| Good Largest Contentful Paint | 53.5% | 81.1% | 65.6% |
| Good Interaction to Next Paint | 59.7% | 85.0% | 79.4% |
| Median JavaScript per page | 822 KB | 409 KB | 732 KB |
| Median page weight | 2,170 KB | 1,358 KB | 2,564 KB |
Source: HTTP Archive technology report API, Core Web Vitals and page weight endpoints, read 21 October 2026. For comparison, the Web Almanac 2025 puts the median mobile home page at 632 KB of JavaScript.
Read the table with care. These are different populations: many Next.js sites are applications, and many Astro sites are small and new. It shows where each framework's defaults lead, not what your site will score after a move. Our own measurement on two sites we built points the same way: an article page shipped about 43 KB of compressed JavaScript on Astro and about 287 KB on a prerendered Next.js 16 site (Astro vs Next.js has the details). Our website platform speed report breaks the same field data down by country.
The migration pays when three things are true:
- Most routes are the same for every visitor. Pages change when someone publishes, not when someone signs in.
- Your field data shows the cost. Search Console's Core Web Vitals report or your own real-user data shows slow LCP or INP on content pages, and the JavaScript is first-party framework code, not one heavy chat widget.
- The site is one team's marketing surface, separate from the product, so it can live on its own release schedule.
If only the third is true, splitting the site (Astro on www, Next.js on app) may be all you need. If none is true, stay.
What maps to what
Astro's official migration guide covers the syntax: className becomes class, {children} becomes <slot />, <Link> becomes <a>, and layouts become .astro files with their own <html> and <head>. The table below covers the rest of a real Next.js project, with each line checked against both frameworks' current docs.
| In Next.js | In Astro | What to watch |
|---|---|---|
app/**/page.tsx or pages/*.tsx | src/pages/*.astro | A JSX page file can't be used as a page; convert it or render it inside one |
layout.tsx, _document.tsx | A layout component with <slot /> | Each page needs a full <html>, so one layout carries the head |
generateStaticParams, getStaticPaths | getStaticPaths() | Every dynamic route must list its pages at build |
getStaticProps, fetching in Server Components | fetch() or getCollection() in the frontmatter | Runs at build for static pages |
| MDX files, a CMS | Content collections with a schema | An entry that breaks the schema stops the build |
generateMetadata, metadata | Props passed to one head component | Canonical, hreflang and robots are yours to write |
next/image | <Image /> and <Picture /> from astro:assets | Only in .astro and .mdx; React components use plain <img> |
next/font | The fonts API (fontProviders, <Font />) | Preload only the files above the fold |
Route handlers (route.ts) | Endpoints in src/pages | Static unless prerender = false with an adapter |
| Server Actions | Astro Actions (defineAction) | Runs on a server, so it needs an adapter |
middleware.ts, now proxy.ts | src/middleware.ts | Runs at build time for prerendered pages |
ISR (revalidate) | A rebuild triggered by your CMS, or an on-demand route | No per-page regeneration on a static build |
| Draft Mode | One on-demand preview route | Keep it out of the index |
app/sitemap.ts, robots.ts | @astrojs/sitemap or an endpoint | Use real change dates for lastmod |
next.config.js redirects | Host rules, or redirects with an adapter | Status codes differ; see below |
| React Context across the page | Nano Stores | Context can't wrap separate islands |
NEXT_PUBLIC_ variables | PUBLIC_ variables | Only PUBLIC_ values reach the browser |
| CSS-in-JS (styled-components) | Scoped <style>, CSS Modules or Tailwind | Astro's guide says you may need to replace it |
Sources for the right-hand column: Astro's guides to content collections, images, endpoints, actions, middleware, on-demand rendering, environment variables and fonts. On the Next.js side, version 16 renamed middleware to proxy, and ISR regenerates pages on a timer or on demand.
Two rows change how you run the site, not just how you write it:
- Middleware runs at build time for prerendered pages in Astro, per its middleware guide. If your Next.js middleware rewrites by country, checks cookies or sets headers per request, that logic moves to your host's edge (a Worker, Netlify Edge Functions) or to on-demand routes.
- ISR becomes a rebuild. A static Astro build regenerates everything on each publish. On dardo.studio, an article goes live two to three minutes after the editor publishes, because the CMS triggers a rebuild. That's fine for articles and service pages; prices or stock that change hourly belong in an on-demand route or a client-side fetch.
Keeping React: islands and client directives
Install @astrojs/react and your components work unchanged inside .astro pages. They render to HTML on the server, and they ship JavaScript only if you add a client:* directive. A pricing page might end up like this:
---
// src/pages/pricing.astro
import Layout from "../layouts/Layout.astro";
import PricingCalculator from "../components/PricingCalculator.tsx"; // the same React component
import { getEntry } from "astro:content";
const page = await getEntry("pages", "pricing");
---
<Layout title={page.data.seoTitle} description={page.data.description}>
<h1>{page.data.title}</h1>
<p>{page.data.intro}</p>
<!-- Hydrates only when it scrolls into view; everything else stays HTML. -->
<PricingCalculator client:visible plans={page.data.plans} />
</Layout>The directives, from Astro's framework components guide, are client:load, client:idle, client:visible, client:media and client:only. Three rules catch Next.js teams:
client:onlyskips the server render. Every other directive renders the component to HTML first;client:onlyleaves an empty spot until JavaScript runs. Never use it for content you want indexed.- Props must be serializable. Plain objects, arrays, dates, maps and sets are fine; functions are not, because Astro can't send them to the browser.
- Context doesn't cross islands. Each island hydrates on its own, so a theme or cart provider at the top of the tree no longer reaches every component. Astro's state-sharing recipe recommends Nano Stores, which it describes as under 1 KB.
Start by converting components that never needed interactivity (headers, footers, cards, article bodies) to .astro, and keep React for the parts that do something.
The SEO traps specific to Next.js
Google's site move guide applies even if the domain stays the same: map every old URL to a new one, redirect permanently, expect some ranking fluctuation, and keep redirects "for as long as possible, generally at least 1 year." These are the places a Next.js to Astro move breaks that plan without anyone noticing.
Trailing slashes
Next.js redirects /about/ to /about by default. Astro's default build.format is directory, which writes /about/index.html, and most static hosts serve that at /about/. On Cloudflare Workers, the default HTML handling then answers /about with a 307 to /about/. Every URL on your site has just changed, and the redirect is a temporary one.
You have two clean options:
| Option | Astro config | What happens to existing URLs |
|---|---|---|
| Keep Next.js's URLs | build: { format: "file" }, trailingSlash: "never" | Unchanged; check your host serves /about from about.html without a redirect |
| Switch to slashes | trailingSlash: "always", default directory format | Every URL changes; each old address needs a 301 to its slashed version |
Astro's configuration reference warns that trailingSlash only governs the dev server and on-demand pages, and that the host decides for prerendered ones. Whatever you choose, request a few URLs both ways before launch and read the status codes. On dardo.studio we found our host answering 307 for missing slashes on 17 October and now return a 301; our Astro SEO guide shows the code.
Redirect status codes
Next.js uses 308 for permanent: true and 307 otherwise. Google treats 301 and 308 as permanent and 307 as temporary. Astro's redirects setting, on a static site without an adapter, produces a meta refresh page with no status code at all; with an adapter, GET requests get a 301. Carry over every redirect from next.config.js, keep the permanent ones permanent, and put them where your host enforces them. Our redirect map generator builds the old-to-new list from two sitemaps.
Metadata
generateMetadata and metadataBase quietly produced your canonical URLs, Open Graph tags and alternates. In Astro nothing is written unless you write it. Before launch, compare every template's title, description, canonical, robots tag, hreflang set and Open Graph image between the old and new builds. The canonical must match the new URL format exactly.
Sitemap and structured data
app/sitemap.ts becomes @astrojs/sitemap or your own endpoint. Give each URL its content's real change date, not the build time. JSON-LD moves as it is: the same <script type="application/ld+json">, with < escaped as Next.js's own JSON-LD guide recommends.
Content that only existed after hydration
Anything your Next.js pages fetched in useEffect (reviews, prices, a list of posts) was never in the server HTML. Moving it into the Astro frontmatter puts it in the HTML for the first time, which helps; wrapping the old component in client:only keeps it invisible. Google's JavaScript SEO basics explain why server HTML is the safer place.
A step-by-step migration plan
- Inventory every route. Export the URLs from your sitemap, add the Search Console Pages report for the longest range, and list each route as the same for everyone, changing on publish, or different per visitor.
- Decide the URL format (slash or no slash) and write the redirect map before writing code.
- Create the Astro project with
@astrojs/react, plus an adapter only if some routes must render on request. - Build the layout and head component first: one place that writes title, description, canonical, robots, hreflang, Open Graph and JSON-LD from props.
- Port static pages, then dynamic routes with
getStaticPaths(). Convert non-interactive components to.astroas you go. - Move content into collections with a schema, or fetch your CMS at build. Let schema errors stop the build.
- Bring interactive parts over as islands, choose a directive for each, and replace shared Context with Nano Stores.
- Replace server features: route handlers with endpoints or Actions, middleware with edge rules or on-demand routes, Draft Mode with a preview route, ISR with a CMS rebuild hook.
- Images and fonts: switch to
astro:assets, give the LCP image priority, preload one font file. - Compare the builds. Crawl the old and new sites and diff status codes, titles, canonicals, hreflang, JSON-LD and internal links, page by page. Fix the new site until the diff is only what you meant to change.
- Launch and watch. Deploy with the redirects live, submit the new sitemap, and check Search Console's Pages report and your 404 log weekly for at least a month. Keep the redirects for at least a year.
Our website migration SEO checklist goes deeper on steps 10 and 11, and the WordPress to Astro guide covers the same move from the other common starting point.
Pre-launch checklist
| Check | How to confirm it |
|---|---|
| Every old URL answers 200 or a single 301/308 | Run the old URL list through curl -I or a crawler |
| Trailing slash format matches the canonicals | Request each template both ways and read the status |
| Titles, descriptions and canonicals match or improve | Diff the two crawls per URL |
| hreflang sets are complete and reciprocal | Each version lists all versions and itself |
| JSON-LD parses and keeps the same types | Google's Rich Results Test on one page per template |
No client:only around indexable content | Search the code; view source on key pages |
| Islands hydrate and work | Click through forms, menus and calculators on a phone |
| Analytics records page views | Compare a day of traffic before and after |
| Sitemap lists only 200, indexable URLs with real dates | Fetch it and spot-check a dozen entries |
| Field data after launch | Core Web Vitals report after a month of traffic |
When not to migrate
- The product is the site. Dashboards, editors, carts with complex state, real-time data: Next.js's single React app is the right tool, and Astro's own migration guide says such features "may require advanced Astro techniques."
- The slowness isn't the framework. If one tag manager, chat widget or video embed causes your slow interactions, fix that first; our INP guide shows how to find the script.
- The team is all-in on Next.js and ships product and marketing from one codebase every week. A second framework is a real cost; splitting
wwwfromappis only worth it when the two have different owners or schedules. - You need ISR at scale. Tens of thousands of pages that change hourly are easier to keep fresh with Next.js's caching than with full rebuilds.
What to do next
Look up your platform's numbers in our website platform speed report, then build your redirect map before anything else. If you want a team that works in both frameworks to do the move, see Astro development, website migration and, for what stays on Next.js, Next.js development.
Common questions
Can I reuse my React components in Astro?
Yes. Install @astrojs/react and import the components into .astro pages. They render to HTML on the server, and ship JavaScript only if you add a client directive such as client:visible. Props must be serializable, and React Context no longer reaches across separate islands.
Will migrating from Next.js to Astro hurt my SEO?
Not if every URL keeps its address or gets a permanent redirect, and titles, canonicals, hreflang and structured data carry over. Google warns that rankings can fluctuate during any site move. The usual cause of lasting losses is a changed trailing-slash format or missing redirects.
Does Astro have ISR like Next.js?
Not as such. A static Astro site rebuilds when content changes, usually triggered by a webhook from the CMS, and routes that need fresher data can be rendered on demand with an adapter. For tens of thousands of pages that change hourly, Next.js caching is easier.
What replaces Next.js API routes and Server Actions in Astro?
Endpoints in src/pages replace route handlers: they produce static files at build, or run on request with an adapter. Astro Actions replace Server Actions with type-checked server functions, and they need a server adapter to run.
How long should I keep redirects after the migration?
Google's site move guide says to keep them as long as possible, generally at least one year. Old links and bookmarks keep arriving for much longer, so most sites keep them indefinitely.
When should I not migrate from Next.js to Astro?
When the site is mostly a signed-in application, when slowness comes from third-party scripts rather than the framework, or when one team ships product and marketing together in one Next.js codebase. In those cases fix the scripts or split the marketing site instead.
Sources & further reading
Sources behind this page, with further detail from the original publishers.
- Astro Docs: Migrating from Next.jsdocs.astro.build
- HTTP Archive: Tech Reporthttparchive.org
- HTTP Archive technology report API: Core Web Vitals, Next.js and Astro (September 2026)cdn.httparchive.org
- HTTP Archive technology report API: page weight, Next.js and Astro (September 2026)cdn.httparchive.org
- HTTP Archive: Web Almanac 2025, Page Weight chapteralmanac.httparchive.org
- Astro Docs: Content collectionsdocs.astro.build
- Astro Docs: Imagesdocs.astro.build
- Astro Docs: Endpointsdocs.astro.build
- Astro Docs: Actionsdocs.astro.build
- Astro Docs: Middlewaredocs.astro.build
- Astro Docs: On-demand renderingdocs.astro.build
- Astro Docs: Environment variablesdocs.astro.build
- Astro Docs: Fontsdocs.astro.build
- Astro Docs: Front-end frameworks (client directives)docs.astro.build
- Astro Docs: Share state between islandsdocs.astro.build
- Astro Docs: Configuration referencedocs.astro.build
- Next.js Docs: proxy.js (formerly middleware)nextjs.org
- Next.js Docs: Incremental Static Regenerationnextjs.org
- Next.js Docs: trailingSlashnextjs.org
- Next.js Docs: redirectsnextjs.org
- Next.js Docs: JSON-LDnextjs.org
- Cloudflare Docs: Workers static assets HTML handlingdevelopers.cloudflare.com
- Google Search Central: Site moves with URL changesdevelopers.google.com
- Google Search Central: Redirects and Google Searchdevelopers.google.com
- Google Search Central: JavaScript SEO basicsdevelopers.google.com
