For years, Gatsby was the gold standard for the JAMstack. But as the web evolved, the “heavy hydration” problem became a bottleneck. In my recent projects, I’ve noticed a recurring theme: developers are exhausted by Gatsby’s build times and the massive JavaScript bundles sent to the browser. This is why I’ve been spending more time exploring Astro. If you’re wondering how to migrate Gatsby to Astro, you’re likely looking for better performance and a simpler developer experience.

The transition isn’t a one-click process, but it’s surprisingly manageable because Astro allows you to bring your existing React components with you. Whether you’re questioning if JAMstack is still worth it in 2026 or simply want to optimize your Lighthouse scores, this guide will walk you through the migration path I’ve refined over several production sites.

Prerequisites

Before we dive into the code, make sure you have the following ready:

Step 1: Initialize Your Astro Project

Don’t try to convert your Gatsby folder in place. Start fresh to avoid dependency hell. I recommend using the Astro CLI to scaffold a clean environment.

npm create astro@latest

During the setup, choose the “Empty” template. This gives you full control over the structure. Once installed, add the React integration since most of your Gatsby components are likely written in React:

npx astro add react

Step 2: Mapping Your Content Source

Gatsby uses a GraphQL data layer, which is often the most complex part of the migration. Astro takes a different approach: it fetches data directly using standard fetch() or dedicated SDKs in the frontmatter.

If you use Markdown/MDX:

Move your content/ folder from Gatsby to Astro’s src/content/ directory. Use Astro’s Content Collections to define your schema. It provides the same type-safety as Gatsby’s GraphQL but without the overhead.

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    pubDate: z.date(),
    description: z.string(),
  }),
});

export const collections = { blog };

If you use a Headless CMS:

Replace your gatsby-source-xxx plugins with the CMS’s own JavaScript SDK. Instead of querying GraphQL in a template, you’ll fetch the data directly in the Astro component’s script section (the “code fence”).

Comparison of Gatsby GraphQL query versus Astro frontmatter data fetching
Comparison of Gatsby GraphQL query versus Astro frontmatter data fetching

Step 3: Migrating Components and Logic

This is where Astro shines. You don’t have to rewrite your UI from scratch. You can move your React components into src/components/ and use them directly.

However, remember the Astro Islands architecture. By default, components are rendered as static HTML. If your Gatsby component relies on useEffect or useState, you must tell Astro to hydrate it on the client:

---
import MyReactComponent from '../components/MyReactComponent';
---




In my experience, moving to a “zero-JS by default” mindset is the hardest but most rewarding part of the migration. If you’re weighing this against other frameworks, you might find our comparison of Astro vs Next.js for static sites useful.

Visualization of Astro Islands architecture showing static HTML and hydrated React components
Visualization of Astro Islands architecture showing static HTML and hydrated React components

Step 4: Handling Routing and Redirects

Gatsby uses a file-based routing system similar to Astro, but there are differences in how dynamic routes are handled. In Gatsby, you’d use gatsby-node.js to create pages. In Astro, you use getStaticPaths().

Gatsby approach (gatsby-node.js):
createPages({ pages: ... })

Astro approach (src/pages/blog/[slug].astro):

---
export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({ params: { slug: post.slug } }));
}
const { post } = Astro.props;
---

To maintain your SEO, ensure your URL structures remain identical. If you must change a URL, use an astro.config.mjs redirect or a _redirects file if you are deploying Astro to GitHub Pages or Netlify.

Pro Tips for a Smoother Migration

Troubleshooting Common Issues

Issue: “Window is not defined” errors in React components.
Fix: This happens because Astro renders components on the server first. Wrap browser-specific logic (like localStorage) inside a useEffect hook or use the client:only="react" directive.

Issue: Build times are still slow.
Fix: Check your image sizes. While Astro is fast, processing 1,000+ unoptimized images can still lag the build. Leverage Astro’s remote image optimization.

What’s Next?

Now that you’ve migrated, it’s time to optimize. I recommend auditing your site with PageSpeed Insights. You’ll likely see a dramatic jump in your “First Contentful Paint” (FCP) and “Total Blocking Time” (TBT) because you’ve stripped away the Gatsby hydration overhead.

If you’re looking for more ways to automate your workflow or explore new tools, check out my other guides on technical productivity.