A Gatsby migration is mostly mechanical work: hundreds of small, similar transformations (GraphQL query to content-collection call, GatsbyImage to Image, Link to a) interleaved with a few decisions that need a human. That profile is exactly what AI coding agents are good at in 2026, and also exactly where they go wrong if left unsupervised. This is how we use them on real Gatsby-to-Astro and Gatsby-to-Next.js migrations, what they reliably do well, and where we stop trusting them.
Where agents help
1. Generating codemods instead of hand-editing
The highest-leverage use is not asking an agent to "migrate the site". It is asking it to write a codemod for one transformation, reviewing the codemod, and running it across the codebase. A codemod is testable; a hundred freehand edits are not.
Example prompt, given to an agent with the repo checked out:
Write a jscodeshift transform that finds every
import { Link } from 'gatsby'and every JSX<Link to="...">, rewrites the import toimport Link from 'next/link', and renames thetoprop tohref. Leave any other props unchanged. Include three test fixtures: a plain link, a link with className, and a link with a template-literalto.
The output is a transform plus fixtures you can run with npx jscodeshift -t transforms/gatsby-link.js src/. If a fixture fails, the agent gets the failure and fixes the transform. If it passes, you have a repeatable change and a record of what it did.
Transformations that suit this approach on a Gatsby migration:
gatsbyLinkto framework linkGatsbyImage/StaticImagetoastro:assetsornext/imageuseStaticQuery+graphqlfor site metadata to a plain importnavigate()fromgatsbyto the framework routerreact-helmet<Helmet>blocks to metadata exports (Next.js) or layout props (Astro)
2. Porting components one at a time
For components that do not reduce to a codemod (layout components, anything with Gatsby-specific data props), an agent-driven port works well when the task is framed narrowly:
Port
src/components/PostCard.jsxtosrc/components/PostCard.astro. It currently receives apostprop shaped by theallMarkdownRemarkquery insrc/pages/blog.js. It will now receive aCollectionEntry<'posts'>from thepostscollection defined insrc/content.config.ts. Keep the markup and class names identical. Do not add client-side JavaScript.
The key constraints are the ones that prevent the common failures: keep markup identical (so visual diffs stay clean), say where the data now comes from (so the agent does not invent a shape), and forbid client JavaScript (so a static component does not grow a client:load it never needed).
3. Translating the GraphQL layer
Agents are good at reading a Gatsby page query and producing the equivalent collection filter or source-module call, because it is a translation task with both sides visible. They are also good at writing the Zod schema for a content collection from a sample of real frontmatter; give them ten representative files and ask for a schema that validates all of them, then run astro check across the full set to find the outliers.
4. Automated visual-diff verification
This is where we get most of the value back. Build the old Gatsby site and the new site, crawl the same URL list, and screenshot both:
// scripts/visual-diff.ts
import { chromium } from 'playwright';
import { readFileSync } from 'node:fs';
const urls = readFileSync('urls.txt', 'utf8').split('\n').filter(Boolean);
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
for (const path of urls) {
for (const [name, base] of [['old', 'http://localhost:9000'], ['new', 'http://localhost:4321']]) {
await page.goto(base + path, { waitUntil: 'networkidle' });
await page.screenshot({ path: `shots/${name}${path.replace(/\//g, '_') || '_root'}.png`, fullPage: true });
}
}
await browser.close();
Compare the pairs with pixelmatch or Playwright's built-in toHaveScreenshot, and hand the agent the list of pages that differ. "Fix the layout difference on /blog/foo/ — here are the two screenshots and the diff" is a task agents complete reliably, because the target is unambiguous. The same loop works for <head> parity: dump the metadata from both builds and diff the JSON.
Where agents fail, and how we contain it
They invent replacements. Asked to replace a Gatsby plugin, an agent will happily install a package that does not exist or is abandoned. Every dependency an agent adds goes through a human check of the registry page and the last publish date.
They hydrate everything. Left alone on an Astro port, agents add client:load to components that were never interactive, recreating Gatsby's ship-all-the-JavaScript problem. We grep for client directives in review and require a reason for each.
They change markup "to improve it". Unrequested markup changes break visual diffs and, worse, break CSS selectors and analytics hooks. The instruction to keep markup identical has to be explicit and repeated.
They are confident about SEO they have not checked. An agent will tell you redirects are in place. Verify with curl -I against staging, for every URL in the old sitemap, in CI. The comm -23 old.txt new.txt sitemap diff from our Astro migration tutorial is the check that catches it.
They do not know your traffic. Which pages carry the backlinks, which templates matter, which redirect chains are already three deep: this is Search Console and log data, and it belongs in the brief, not in the agent's guesswork.
A realistic workflow
- A human does the plugin and GraphQL audit and writes the target mapping. This is the design work; it is not delegated.
- Agents write codemods for the mechanical transformations, with fixtures. Humans review and run them.
- Agents port components one at a time against explicit constraints. Humans review the diff per component.
- Visual-diff and metadata-diff scripts run on every change. Agents fix the flagged differences; humans sign off on pages with intentional changes.
- Humans own the redirect map, the cutover, and the post-cutover Search Console watch.
On the migrations we have run this way, the agent-assisted steps are the ones that used to consume the most engineer-hours for the least judgement, which is where you want the speed-up. The parts that need judgement are still done by people who have migrated Gatsby sites before. If you want that combination on your migration, contact us.