The prompt that converted this site from Next.js to Astro
A single prompt does not convert a Next.js site to Astro. A 330-line plan with ten phases and a done check per phase does, when an agent works through it. This site went from Next.js 13 with 15 runtime dependencies and 17 React pages to Astro with 8 dependencies and zero JavaScript bundles in one day, 2026-07-27.
Why a plan and not a prompt
The searches that bring people to this page ask for a conversion prompt: one message that turns a Next.js repo into an Astro one. I did not have one. What I had was a markdown file, 330 lines long, with ten phases numbered 0 to 9. Each phase states an outcome, a task list, and a done check. The first rule in the file is the one that matters: do not move to the next phase until the done check passes.
A prompt says what you want. A plan says how you will know you got it. The agent that ran this plan could not skip discovery because Phase 1 needed the route list from Phase 0. It could not skip redirects because Phase 7’s done check reads every URL from the discovery file and expects a 200 or a 301. The plan has four more rules: commit at the end of every phase, never deploy, record judgement calls in a decisions file, and write blockers to a blockers file instead of stopping. There is exactly one hard gate, and I come to it below.
What the Next.js site was
Before touching anything, the agent wrote a discovery file. That file says the old site was Next.js 13 on the pages router, React 18, Chakra UI 2 with Emotion and framer-motion, and yarn 1. It had 15 runtime dependencies, 8 dev dependencies, and 69 TypeScript files across 22 component folders.
It served 17 routes. Seven of them were blog posts from November and December
2021, and every one of those posts was a .tsx file. The prose lived inside
React components, next to a Calendly popup and a Formspark contact form. Four
more routes were service pages for an agency I no longer run.
That is the real conversion cost, and no migration guide mentions it. The
guides say rename pages/*.jsx to src/pages/*.astro and swap <Link> for
<a>. That works when the content has a format of its own. Mine did not. The
content had to be pulled out of components before anything could be converted,
and once it was out, most of it turned out not to be worth converting.
Phase 0: write down what exists before touching it
The plan’s first phase produces no code. It produces a file that lists every route, every project entry with its URL, every social link, and every piece of prose on the site. The plan’s own justification: the project list is reused in Phase 7, and losing it means re-scraping the live site.
For a person, discovery is optional because the person remembers. For an agent,
it is the only memory that survives the archive step. When the agent moved the
old source into a folder called _archive, the discovery file was the one
document that still described what the site used to do. Every later done check
reads from it.
The one place the agent was told to ask
Phase 1 chooses the framework. The default is Astro. The gate reads: if the repo is Next.js or Remix and the human has strong reason to stay there, this is the one place to ask. Post the tradeoff in one short message and wait. Everywhere else, proceed.
Why that gate and no others? Because the framework choice is the only decision in the plan that a done check cannot settle. Whether a build passes, whether a link resolves, whether a draft leaks into the sitemap: those are checkable. Whether to leave React is taste and history. I had already built this site in Next.js, Remix, and Astro and knew the answer, so the gate opened and closed in one message. But it was there, and its position in the plan is the design.
The agent made 13 recorded decisions on that first day without asking. It
picked pnpm over yarn because the done checks were written as pnpm build. It
used Astro 7 when the plan said Astro 5, because the APIs it needed were the
same shape. It replaced the sitemap integration with a custom endpoint because
the integration could not exclude drafts. Each one is a dated line in the
decisions file, with the plan’s wording and the reason for diverging.
What came across and what did not
The archive commit moved 124 files in one step. Nothing was deleted; the plan says to keep the old source until Phase 7 confirms nothing references it.
What migrated: the identity data, the project list with its stack labels and URLs, and the social links. That is it.
What did not: all seven blog posts and all four service pages. The posts were
generic agency SEO content with no first-person substance, and the decisions
file says so in those words. They were retired, not converted. The old
/blog/:slug routes now return a 301 to /writing; /portfolio goes to
/projects; /contact goes to /about; only /services/* falls back to the
homepage.
So the honest name for what happened is not conversion. It is retirement plus rebuild, with redirects for everything that used to have a URL. I think that is true of most Next.js to Astro migrations on personal sites. The framework change is the excuse; the content audit is the work.
The build after
The Astro site has 8 runtime dependencies and 17 dev dependencies. The source tree is 57 files. A production build takes about 3 seconds and produces 940 KB on disk, and 608 KB of that is self-hosted fonts. There are no JavaScript bundles. The only scripts on any page are two inline snippets for theme persistence and two JSON-LD blocks.
The framework comparison lives in the three frameworks essay. This log is about the process, and the process number I care about is one day from discovery to a site that passed its own verification phase: build clean, Lighthouse at or above 95 on all four categories, every old URL resolving or redirecting, no draft in any feed.
What I would put in the plan if I did it again
Three things the plan did not say, the agent did not guess, and I found later.
Robots rules for hashed asset paths. The scaffold’s robots.txt disallowed
/_astro/ for every user agent. That folder holds the only stylesheet and both
preloaded fonts, so Googlebot rendered every page unstyled for two weeks. The
fix landed on 2026-08-10, and the crawler log was the
only reason I found it.
The plan should say: never disallow a path that serves CSS or fonts.
Redirects for URLs you forgot you had. The plan’s redirect map came from the
discovery file, and the discovery file came from the running site. But the
running site had already dropped an older /posts/* scheme. The crawler log
showed 11 distinct /posts/* URLs still being requested, about 30 hits in 13
days, all returning 404. The plan should say: pull the redirect list from
Search Console and server logs, not only from the current routes.
A pinned TypeScript version. The first build failed astro check until
TypeScript was pinned, and that was the second commit of the day. It is a small
thing. It is also exactly the kind of thing a plan exists to hold, so the next
run does not rediscover it.
If you want the prompt, that is it: a plan with done checks, one gate for the decision only a human can make, a decisions file for every judgement call, and a log that tells you what the plan missed.
Revisions
- Published, two days before its slot: the publish-day demand check swapped it with the ag-grid-url-sync grammar reference (11 impressions against 4). Opening trimmed from 67 to 57 words; linked the crawler-log post.
- Created as an outline. Sourced from Search Console: the query "astro to next.js conversion prompt" earned 12 impressions at position 25 with no dedicated post.
- Body written from the rebuild plan, DISCOVERY.md, and the decision log of 2026-07-27.