Blog

Next.js to Astro migration: when it pays, what maps to what, and a plan that keeps your rankings

Move a content site from Next.js to Astro when it ships too much JavaScript: what maps to what, URL and redirect traps, a step-by-step plan and when to stay.

By Nicolás Cerón ·

A lamplit vineyard terrace at blue hour where a young grafted vine is bound to old rootstock with a crimson ribbon.
On this page

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 2026Next.jsAstroAll sites
Sites measured395,34547,2778,569,446
Pass all Core Web Vitals35.7%71.1%52.8%
Good Largest Contentful Paint53.5%81.1%65.6%
Good Interaction to Next Paint59.7%85.0%79.4%
Median JavaScript per page822 KB409 KB732 KB
Median page weight2,170 KB1,358 KB2,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.jsIn AstroWhat to watch
app/**/page.tsx or pages/*.tsxsrc/pages/*.astroA JSX page file can't be used as a page; convert it or render it inside one
layout.tsx, _document.tsxA layout component with <slot />Each page needs a full <html>, so one layout carries the head
generateStaticParams, getStaticPathsgetStaticPaths()Every dynamic route must list its pages at build
getStaticProps, fetching in Server Componentsfetch() or getCollection() in the frontmatterRuns at build for static pages
MDX files, a CMSContent collections with a schemaAn entry that breaks the schema stops the build
generateMetadata, metadataProps passed to one head componentCanonical, hreflang and robots are yours to write
next/image<Image /> and <Picture /> from astro:assetsOnly in .astro and .mdx; React components use plain <img>
next/fontThe fonts API (fontProviders, <Font />)Preload only the files above the fold
Route handlers (route.ts)Endpoints in src/pagesStatic unless prerender = false with an adapter
Server ActionsAstro Actions (defineAction)Runs on a server, so it needs an adapter
middleware.ts, now proxy.tssrc/middleware.tsRuns at build time for prerendered pages
ISR (revalidate)A rebuild triggered by your CMS, or an on-demand routeNo per-page regeneration on a static build
Draft ModeOne on-demand preview routeKeep it out of the index
app/sitemap.ts, robots.ts@astrojs/sitemap or an endpointUse real change dates for lastmod
next.config.js redirectsHost rules, or redirects with an adapterStatus codes differ; see below
React Context across the pageNano StoresContext can't wrap separate islands
NEXT_PUBLIC_ variablesPUBLIC_ variablesOnly PUBLIC_ values reach the browser
CSS-in-JS (styled-components)Scoped <style>, CSS Modules or TailwindAstro'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:

  1. client:only skips the server render. Every other directive renders the component to HTML first; client:only leaves an empty spot until JavaScript runs. Never use it for content you want indexed.
  2. 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.
  3. 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:

OptionAstro configWhat happens to existing URLs
Keep Next.js's URLsbuild: { format: "file" }, trailingSlash: "never"Unchanged; check your host serves /about from about.html without a redirect
Switch to slashestrailingSlash: "always", default directory formatEvery 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

  1. 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.
  2. Decide the URL format (slash or no slash) and write the redirect map before writing code.
  3. Create the Astro project with @astrojs/react, plus an adapter only if some routes must render on request.
  4. Build the layout and head component first: one place that writes title, description, canonical, robots, hreflang, Open Graph and JSON-LD from props.
  5. Port static pages, then dynamic routes with getStaticPaths(). Convert non-interactive components to .astro as you go.
  6. Move content into collections with a schema, or fetch your CMS at build. Let schema errors stop the build.
  7. Bring interactive parts over as islands, choose a directive for each, and replace shared Context with Nano Stores.
  8. 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.
  9. Images and fonts: switch to astro:assets, give the LCP image priority, preload one font file.
  10. 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.
  11. 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

CheckHow to confirm it
Every old URL answers 200 or a single 301/308Run the old URL list through curl -I or a crawler
Trailing slash format matches the canonicalsRequest each template both ways and read the status
Titles, descriptions and canonicals match or improveDiff the two crawls per URL
hreflang sets are complete and reciprocalEach version lists all versions and itself
JSON-LD parses and keeps the same typesGoogle's Rich Results Test on one page per template
No client:only around indexable contentSearch the code; view source on key pages
Islands hydrate and workClick through forms, menus and calculators on a phone
Analytics records page viewsCompare a day of traffic before and after
Sitemap lists only 200, indexable URLs with real datesFetch it and spot-check a dozen entries
Field data after launchCore 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 www from app is 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.