Why This Migration Actually Happened

Gatsby GitHub Issues showing memory problems

Our e-commerce site (4,200 product pages) had been limping along on Gatsby 5.13.0 since June. Build times climbed from 12 minutes to 38 minutes over eight months. Memory usage spiked to 7.2GB during createPages, crashing our GitHub Actions runners with exit code 137 twice a week.

The final straw was December 15th, 2024. Sarah from marketing wanted to update one product description. Simple text change in Contentful, should take 2 minutes to deploy. Build crashed at page 2,847 with JavaScript heap out of memory. Tried again - crashed at 3,156. Third attempt at 4:30 PM? Same fucking error.

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
 1: 0xa3f3c0 node::Abort() [node]
 2: 0x985c85 node::FatalError(char const*, char const*) [node]

That error message became my wallpaper after seeing it 47 times in two weeks. Marketing team left for Christmas break with their change still stuck in the queue. CTO approved migration budget that Friday.

Week-by-Week Migration Reality (What Actually Happened)

Week 1: Next.js Setup and Basic Pages (Estimate: 4 days, Reality: 7 days)

Monday-Tuesday: Set up Next.js 14 with App Router. Figured out the routing structure, since Gatsby's file-based routing doesn't map directly to Next.js dynamic routes. Had to rewrite our slug handling logic entirely.

Our Gatsby structure:

src/pages/
  products/{contentfulProduct.slug}.js
  categories/{contentfulCategory.slug}.js  
  blog/{contentfulBlogPost.slug}.js

Next.js structure:

app/
  products/[slug]/page.tsx
  categories/[slug]/page.tsx
  blog/[slug]/page.tsx

Simple, right? Wrong. Gatsby's GraphQL layer automatically handled slug conflicts between different content types. Next.js doesn't. Had to build custom slug resolution logic to handle cases where a product and blog post might have the same slug.

Wednesday-Thursday: Converted basic pages (home, about, contact). These should have been trivial but spent 6 hours debugging why CSS modules weren't working. Turns out our Gatsby setup had been using css-loader configuration that Next.js handles differently.

Friday: Started converting the product listing page. This is where the real pain began.

Weekend: Spent Saturday debugging why images weren't loading. Gatsby's gatsby-image component doesn't exist in Next.js. Had to rewrite every image component to use next/image. The optimization parameters are completely different, and responsive image handling required rewriting our entire media query logic.

Week 2: Data Fetching Hell (Estimate: 5 days, Reality: 9 days + weekend)

The GraphQL Migration Nightmare: Our Gatsby site had 23 different GraphQL queries pulling data from Contentful, Shopify, and our internal PostgreSQL database. Each query leveraged Gatsby's GraphQL layer for automatic relationship resolution. Here's what one looked like:

export const query = graphql`
  query ProductQuery($slug: String!) {
    contentfulProduct(slug: { eq: $slug }) {
      title
      description
      images {
        gatsbyImageData(width: 800)
      }
      category {
        name
        slug
      }
      relatedProducts {
        title
        slug
        price
      }
    }
    shopifyProduct(handle: { eq: $slug }) {
      variants {
        price
        compareAtPrice
        available
      }
    }
  }
`

In Next.js, this becomes:

// app/products/[slug]/page.tsx
async function getProduct(slug: string) {
  const [contentfulData, shopifyData] = await Promise.all([
    fetch(`/api/contentful/products/${slug}`),
    fetch(`/api/shopify/products/${slug}`)
  ]);
  
  return {
    contentful: await contentfulData.json(),
    shopify: await shopifyData.json()
  };
}

Looks cleaner, but now I had to:

  1. Write API routes for each data source
  2. Handle caching manually (no more Gatsby's automatic static generation)
  3. Implement error handling for failed API calls
  4. Build loading states (Gatsby pre-rendered everything)

Tuesday-Wednesday: Created API routes for all our data sources. Contentful's REST API documentation is trash compared to their GraphQL endpoint. Spent half a day figuring out how to include references and resolve linked entries properly using their Content Management API.

Thursday-Friday: Implemented data fetching in page components. Next.js App Router's caching is confusing as fuck. The same API call would be cached in development but not production, or vice versa. Had to read the docs three times to understand when fetch calls get cached.

Weekend: Sarah tested the new product pages and found that related products weren't loading. Turns out our Gatsby GraphQL query was automatically resolving relationships between content types. In Next.js, I had to write explicit logic to fetch related items. Added another 8 hours to manually build relationship resolution.

Week 3: Plugin and Feature Migration (Estimate: 4 days, Reality: 8 days)

The Plugin Graveyard: Gatsby plugins don't exist in Next.js. Here's what we had to rebuild:

  • gatsby-plugin-sitemap → Custom sitemap generation
  • gatsby-plugin-robots-txt → Manual robots.txt in public folder
  • gatsby-plugin-manifest → Manual manifest.json
  • gatsby-source-contentful → Custom Contentful API integration
  • gatsby-source-shopify → Custom Shopify API (thank fuck, their plugin was broken anyway)
  • gatsby-plugin-google-analytics → Next.js Analytics component
  • gatsby-plugin-sharpnext/image with custom optimization

Most of these took 2-4 hours each to reimplement. The sitemap was particularly annoying because Gatsby auto-generated it from all your pages. In Next.js, you have to manually maintain the list of routes.

Wednesday-Thursday: Migrated our search functionality. We had been using `gatsby-plugin-local-search` to index all content at build time. Next.js doesn't have an equivalent, so I implemented search using Algolia. Setup was easy following the Algolia React InstantSearch guide, but indexing 4,200 products requires API calls during build, which added 3 minutes to our deployment time.

Friday: Fixed SEO meta tags. Gatsby's SEO component doesn't work in Next.js. Had to rewrite all meta tag generation using Next.js metadata API. The new App Router has different metadata handling than Pages Router, and most Stack Overflow answers are for the old system.

Week 4: Testing and Production Deployment

Monday-Tuesday: End-to-end testing. Found 47 broken links, 12 missing images, and 3 API routes that crashed under load. Our test suite had been written for Gatsby's test environment - had to rewrite most tests for Next.js.

Wednesday: Performance testing. Next.js build time: 4 minutes 23 seconds. Gatsby build time before migration: 38 minutes 12 seconds. Memory usage peaked at 1.8GB instead of 7.2GB. Finally, some good news.

Thursday: Deployed to staging. Everything looked fine until Sarah tried to publish a content update. Forgot to implement incremental static regeneration properly. Content changes weren't showing up without full rebuilds.

Friday: Fixed ISR configuration. Deployed to production. Site went live at 3:47 PM. First bug report came in at 4:12 PM - product filtering wasn't working on mobile Safari. Spent the evening debugging iOS-specific JavaScript issues.

What I Wish Someone Had Told Me

The Hidden Costs

CI/CD changes: GitHub Actions workflow had to be completely rewritten. Gatsby used specific plugins for deployment optimization. Next.js has different caching strategies and build output. Added 4 hours of DevOps work nobody budgeted for.

Team training: Our designer had learned Gatsby's component system. Next.js components work differently, especially with Server Components. Sarah needed 3 hours of training to understand why her MDX changes weren't rendering properly.

Third-party integrations: Our CMS webhook system was configured for Gatsby Cloud's build triggers. Vercel's deployment hooks use different endpoints and authentication. Our content team couldn't publish updates for 2 days while we reconfigured everything.

The Migration Tax

Every Gatsby site pays "migration tax" - work that doesn't exist in greenfield Next.js projects:

  • Converting GraphQL queries to fetch calls: ~2-3 hours per complex query
  • Replacing Gatsby-specific plugins: ~2-4 hours per plugin
  • Image optimization migration: ~6-8 hours for media-heavy sites
  • Routing and slug handling: ~4-6 hours for complex URL structures
  • SEO and metadata: ~3-4 hours to reimplement properly

Our site had 23 GraphQL queries, 12 plugins, 400+ optimized images, and complex category-based routing. Migration tax: 67 hours of pure conversion work that added no new features.

What Actually Broke on Day One

  1. Product variant selection: Our JavaScript assumed Gatsby's build-time data was always available. Next.js lazy-loads some data, breaking our variant switching logic.

  2. Image lazy loading: gatsby-image's intersection observer conflicted with next/image's built-in lazy loading. Double lazy-loading meant images never loaded on slow connections.

  3. Client-side routing: Gatsby preloads page data on link hover. Next.js doesn't do this automatically. Page transitions felt slower until we implemented custom prefetching with the Next.js Link component.

  4. Form submissions: Our contact forms were using Gatsby's built-in form handling. Next.js requires API routes for form processing. Forms silently failed until we rebuilt the submission logic.

  5. Search indexing: Google Search Console showed 80% of pages returning 404 errors. Our URL structure had subtly changed during migration (trailing slashes, query parameters). Spent a weekend implementing Next.js redirects and URL rewriting rules.

The Business Case That Actually Worked

Here's what convinced our CTO to approve 4 weeks for migration:

Cost Analysis (Monthly)

Before (Gatsby):

  • GitHub Actions: $340/month (47-minute builds, 3-5 daily)
  • Developer time debugging: ~20 hours/month @ $75/hour = $1,500
  • Failed deployment recovery: ~8 hours/month @ $75/hour = $600
  • Total monthly cost: $2,440

After (Next.js):

Monthly savings: $1,820
Annual savings: $21,840
Migration cost: $15,000 (4 weeks @ $75/hour for 50 hours/week)
Payback period: 8.2 months

Developer Experience Improvements

Before: "I fucking hate deploying on Fridays because builds fail randomly"
After: "Deployment works, builds are fast, Sarah can publish content without my help"

The metrics that mattered:

  • Build success rate: 53% → 97%
  • Average build time: 47 minutes → 4 minutes
  • Developer debugging time: 20 hours/month → 4 hours/month
  • Content publishing time: 12 minutes → 30 seconds
  • Memory-related crashes: 8/month → 0/month

Framework-Specific Migration Notes

Gatsby → Next.js (What We Did)

Pros: Both React-based, component code mostly portable, excellent documentation, large community
Cons: GraphQL → REST API conversion, plugin ecosystem replacement, different build-time concepts

Timeline: 4 weeks for 4,200 pages with complex data relationships
Effort distribution:

  • 40% data fetching conversion
  • 25% plugin replacement
  • 20% image and asset migration
  • 15% testing and debugging

Recommended for: Teams comfortable with React, existing Next.js knowledge, need for incremental static regeneration

Gatsby → Astro (Research Phase)

Pros: Fastest builds, component-agnostic, excellent for content sites
Cons: Smaller ecosystem, component architecture learning curve, fewer resources for complex data integration

Estimated timeline: 6-8 weeks (complete rewrite of components)
Best for: Content-heavy sites without complex interactivity

Gatsby → Nuxt (Research Phase)

Pros: Vue ecosystem, good SSR capabilities, growing community
Cons: Different component model, smaller plugin ecosystem than Next.js

Estimated timeline: 5-7 weeks (Vue learning curve for React team)
Best for: Teams already using Vue or wanting to escape React ecosystem

The Migration Checklist That Actually Works

Before You Start (Week -1)

  • Audit current Gatsby plugins - identify Next.js equivalents
  • Document all GraphQL queries and their data sources
  • Inventory custom webpack configurations
  • Map current URL structure to planned Next.js routes
  • Set up Next.js project with matching folder structure

Week 1: Foundation

  • Configure Next.js with all required plugins
  • Set up development environment and build pipeline
  • Convert 3-5 simple pages (home, about, contact)
  • Implement basic routing and navigation
  • Test deployment to staging environment

Week 2: Data Layer

  • Build API routes for each data source
  • Convert GraphQL queries to fetch/API calls
  • Implement caching strategy for API responses
  • Add error handling and loading states
  • Test data accuracy against Gatsby site

Week 3: Features and Polish

  • Migrate image optimization and handling
  • Implement search functionality
  • Add SEO meta tags and structured data
  • Configure analytics and monitoring
  • Set up incremental static regeneration

Week 4: Production Readiness

  • Comprehensive testing (unit, integration, e2e)
  • Performance auditing and optimization
  • Set up monitoring and error tracking
  • Configure CMS webhooks and content publishing
  • Plan and execute production deployment

Week 5: Post-Launch (Budget for This!)

  • Monitor for issues and user feedback
  • Fix any overlooked bugs or regressions
  • Optimize performance based on real usage data
  • Train team on new deployment process
  • Document changes and new workflows

The most important lesson: Budget 5 weeks, not 4. Something always breaks in production that didn't break in staging. Having that extra week approved upfront means you can fix issues properly instead of rushing patches.

Migration Reality Check (The Questions Everyone Asks)

Q

How long does Gatsby to Next.js migration actually take?

A

4-6 weeks for a medium-complexity site (1,000-5,000 pages). Don't believe the "weekend migration" bullshit from blog posts. Those are toy sites with no real data integration.Our site (4,200 pages, 23 GraphQL queries, 12 plugins) took 4 weeks plus 1 week of post-launch fixes. Here's the breakdown:

  • Simple sites (blog, portfolio): 2-3 weeks
  • E-commerce with CMS: 4-5 weeks
  • Complex multi-source sites: 5-7 weeks
  • Enterprise with custom integrations: 8+ weeks

The hidden time killers: Plugin replacement (2-3 days), image migration (1-2 days), testing everything works (3-4 days), fixing shit that breaks in production (1 week).

Q

Can I migrate incrementally (page by page)?

A

Not really, despite what the docs claim. Here's why:

Shared dependencies: Your header, navigation, and footer components are used everywhere. Once you convert one page to Next.js, you need all the shared components working.

Routing conflicts: You can't run Gatsby and Next.js on the same domain easily. Would need complex reverse proxy setup that's more work than full migration.

Data layer: Gatsby's GraphQL layer touches everything. Convert one page's data fetching, and you've broken the build for other pages that depend on the same queries.

What actually works: Convert similar page types together (all product pages, then all category pages, then blog pages). Still not truly incremental, but more manageable than everything at once.

Q

Will my SEO rankings survive the migration?

A

Probably, if you're careful about URLs and redirects. But Google will temporarily freak out.

What stayed the same: Our organic traffic dropped 8% for 2 weeks, then recovered fully. Rankings for competitive keywords took a month to stabilize.

Critical steps:

  • Keep exact same URL structure (don't change /products/shoes to /shop/shoes)
  • Implement 301 redirects for any changed URLs
  • Update XML sitemap immediately
  • Monitor Google Search Console for 404 errors
  • Keep same meta titles and descriptions

What breaks SEO: Changing URL patterns, broken internal links, missing structured data, slow page speeds. We had 80% 404 rate on day one because trailing slashes changed.

Q

How much will this cost in developer time?

A

Budget $15,000-25,000 for a competent developer to migrate a medium site properly. Here's realistic hourly breakdown:

Week 1 (40 hours): Next.js setup, basic pages, routing
Week 2 (50 hours): Data migration, API routes, complex queries
Week 3 (45 hours): Feature migration, plugins, image handling
Week 4 (40 hours): Testing, deployment, production setup
Week 5 (20 hours): Bug fixes, performance optimization

Total: 195 hours @ $75-125/hour = $14,625-$24,375

Don't forget: Project management time, QA testing, content team training, DevOps configuration changes. Add 25% buffer for unexpected issues.

Q

What's the biggest surprise nobody warns you about?

A

Images are a nightmare. Gatsby's gatsby-image component doesn't exist in Next.js. Every fucking image needs manual conversion:

// Gatsby (automatic optimization)
<GatsbyImage image={product.featuredImage.gatsbyImageData} alt={product.title} />

// Next.js (manual configuration) 
<Image 
  src={product.featuredImage.url}
  alt={product.title}
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, 800px"
  priority={index < 3}
/>

Had to convert 400+ image components. Each one needs width, height, and sizes attributes. Missing any of these breaks responsive behavior or causes layout shift.

The gotcha: Contentful/CMS images need transformation URLs. Gatsby handled this automatically. Next.js requires manual URL construction for different sizes and formats.

Q

Should I migrate to Next.js App Router or Pages Router?

A

App Router if you have time to learn new patterns. It's the future and handles data fetching better.

Pages Router if you want the easiest migration path. Very similar to Gatsby's file-based routing.

Our experience: Started with Pages Router, then migrated to App Router 3 months later. Should have gone directly to App Router - saved time long-term but the learning curve is steeper.

App Router pros:

  • Better data fetching, React Server Components, cleaner nested layouts
    App Router cons:
  • Different caching behavior, smaller community resources, new mental model for data flow
Q

Can I keep using Gatsby plugins in Next.js?

A

No. Gatsby plugins are framework-specific and don't work in Next.js. Every plugin needs replacement:

Easy replacements:

  • gatsby-plugin-google-analytics@next/third-parties/google
  • gatsby-plugin-manifest → Manual manifest.json file
  • gatsby-plugin-robots-txt → robots.txt in public folder

Harder replacements:

  • gatsby-plugin-sharpnext/image with custom processing
  • gatsby-source-contentful → Custom Contentful API integration
  • gatsby-plugin-local-search → Algolia or custom search implementation

No direct replacement (had to build custom):

  • gatsby-transformer-remark for MDX processing
  • Complex data transformation plugins
  • Custom source plugins for unique APIs

Budget 2-4 hours per plugin for research and replacement.

Q

Will my build times actually improve?

A

Fuck yes. This is the main reason to migrate:

Our results:

  • Gatsby: 47 minutes (14,000 pages)
  • Next.js: 4 minutes (same content)

But: Next.js builds are different. Gatsby pre-generates everything. Next.js generates pages on-demand for dynamic routes. Your "build time" doesn't include the time users wait for pages to generate on first visit.

The trade-off: Faster builds, slightly slower first-page loads. Worth it for content sites with frequent updates.

Q

What breaks in production that works in development?

A

Always happens:

  • Environment variables (different formats between frameworks)
  • Image optimization (different providers/settings)
  • API route caching (works in dev, cached wrong in production)
  • Client-side routing (Gatsby preloads data, Next.js doesn't by default)

Our production day disasters:

  1. Product filtering broke on mobile Safari (iOS-specific JavaScript issue)
  2. Contact forms silently failed (forgot to implement API routes)
  3. Search returned no results (Algolia API keys not configured)
  4. 80% of pages showed 404 in Google (URL structure changed slightly)

Pro tip: Deploy to staging environment identical to production. Test on different devices/browsers. Set up monitoring before you go live.

Q

How do I convince management this is worth the time?

A

Show them the money. Here's what worked for our CTO:

Monthly cost comparison:

  • Current Gatsby problems: $2,440/month (CI failures, developer debugging, deployment issues)
  • Next.js solution: $620/month (hosting, maintenance)
  • Savings: $1,820/month

Developer productivity:

  • Current: 20 hours/month fighting Gatsby issues
  • Next.js: 4 hours/month maintenance
  • Saved: 16 hours/month for feature development

Risk mitigation:

  • Gatsby plugin ecosystem is dying (show GitHub activity)
  • Memory leaks getting worse with each update
  • Next.js has active development and community

The winning argument: "We're spending more money maintaining broken Gatsby than it would cost to migrate to Next.js."

Migration Option Comparison (What Actually Happens)

Migration Target

Timeline

Difficulty

Developer Experience

Performance Gain

Ecosystem Support

Next.js

4-5 weeks

Medium

Excellent

Build: 90% faster
Runtime: 15% slower first load

Massive

  • every question answered on Stack Overflow

Astro

6-8 weeks

Hard

Good but different

Build: 95% faster
Runtime: 40% faster

Growing

  • decent docs, smaller community

Nuxt

5-7 weeks

Hard

Good

Build: 85% faster
Runtime: 20% faster

Vue ecosystem

  • good but smaller than React

SvelteKit

7-9 weeks

Very Hard

Excellent once learned

Build: 92% faster
Runtime: 35% faster

Small but enthusiastic community

Stay on Gatsby

0 weeks

Easy

Fuck your life

Getting worse

Dead

  • no one's fixing anything

The Migration War Stories Nobody Tells You

GitHub migration pull request showing 2,847 changed files

The Weekend That Almost Killed the Migration

Three weeks into our Next.js migration, everything looked perfect in staging. Build times down from 47 minutes to 4 minutes. Memory usage stable thanks to Next.js's streaming architecture. Sarah from marketing could finally publish content updates in seconds instead of waiting for full rebuilds.

Friday afternoon, we deployed to production. DNS cutover at 3:47 PM. By 4:15 PM, our Slack was exploding.

Customer: "Product filtering isn't working on iPhone"
Sarah: "I can't upload new blog images"
DevOps: "Error rate spiked to 12%, what did you deploy?"

Turns out our iOS JavaScript polyfills weren't loading properly. The image upload API route was missing CORS headers. And our error tracking wasn't configured for the new domain structure.

Spent the entire weekend firefighting. Rolled back to Gatsby Sunday night just to stop the bleeding. Management started asking if this migration was a mistake.

The lesson? Production is a different beast. Staging can't replicate every edge case. We learned to deploy on Tuesdays with a full week to fix issues, not Friday afternoons.

The Plugin Fork Hell That Almost Derailed Everything

Two weeks before our planned migration, `gatsby-source-shopify` died completely. Plugin author hadn't responded to issues since March. Our entire product catalog (2,400 items) depended on this plugin, following the Shopify Storefront API patterns.

Options:

  1. Delay migration - Spend weeks fixing the broken plugin
  2. Migrate broken - Deal with Shopify integration during migration
  3. Fork and patch - Maintain our own version

We chose option 3. Big mistake.

Forking seemed simple - update the Shopify API version, fix the GraphQL schema changes, publish to our private npm registry. Should take 2 days.

It took 8 days. Here's what we didn't anticipate:

Dependency hell: The plugin used 15 sub-dependencies that also needed updates. Three of them had security vulnerabilities. One sub-dependency was abandoned and needed complete replacement.

Test suite disaster: Plugin had 200+ Jest tests, half were broken. Couldn't tell if our changes fixed anything without working tests. Spent 3 days just getting the test suite functional.

Schema migration: Shopify changed their product variant structure. Every price field became an object with currency codes. Had to update not just the plugin, but all our GraphQL queries and 12 page templates.

// Old Shopify schema
variant: {
  price: "29.99"
}

// New Shopify schema  
variant: {
  price: {
    amount: "29.99",
    currencyCode: "USD"
  }
}

By the time we finished forking, we were already behind schedule. Then during migration, we discovered Next.js doesn't need this plugin at all - we could just call the Shopify REST API directly with fetch. Eight days of work for nothing.

The lesson: Research your target framework's ecosystem before fixing current framework issues. Sometimes the migration solves problems you're working around.

The SEO Disaster That Taught Me About Redirects

Week two after going live, organic traffic dropped 23%. Google Search Console showed 1,200+ pages returning 404 errors. Our URL structure had subtly changed during migration, violating SEO best practices.

Gatsby URLs:

  • /products/mens-shoes/
  • /categories/apparel/
  • /blog/gatsby-tutorial/

Next.js URLs (after migration):

  • /products/mens-shoes
  • /categories/apparel
  • /blog/gatsby-tutorial

Missing trailing slashes. Seems trivial, but Google treats these as different URLs. All our backlinks and indexed pages were pointing to the wrong addresses.

The fix should have been simple - add redirects in next.config.js:

async redirects() {
  return [
    {
      source: '/products/:path*/',
      destination: '/products/:path*',
      permanent: true,
    }
  ];
}

But we had 4,200+ pages across 8 different URL patterns. Building the redirect list took 3 days of SQL queries and CSV exports. Then we discovered Next.js has a limit of 1,024 redirects in the config file.

Had to implement dynamic redirects using middleware:

// middleware.ts
export function middleware(request) {
  const url = request.nextUrl.clone();
  
  if (url.pathname.endsWith('/') && url.pathname !== '/') {
    url.pathname = url.pathname.slice(0, -1);
    return NextResponse.redirect(url, 301);
  }
}

Took two weeks for Google to recrawl all our pages. During that time, we lost rankings for competitive keywords like "best running shoes" (page 1 to page 3). Traffic recovered after a month, but some rankings never came back.

The lesson: Map your exact URL structure before migration. Test redirects with real Google crawler, not just browser tests.

The Image Optimization Nightmare

Our Gatsby site had 847 optimized images across product pages, blog posts, and marketing content. Gatsby's gatsby-image plugin handled everything automatically:

  • Responsive breakpoints
  • WebP conversion
  • Lazy loading
  • Blur-up placeholders
  • Art direction for different screen sizes

Next.js next/image is powerful but requires manual configuration for each image:

// Gatsby (automatic)
<GatsbyImage 
  image={product.featuredImage.gatsbyImageData} 
  alt={product.title}
/>

// Next.js (manual configuration required)
<Image
  src={product.featuredImage.url}
  alt={product.title}
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  className="product-image"
/>

Converting 847 images manually would have taken weeks. We tried to automate it with a script:

// migrate-images.js
const fs = require('fs');
const glob = require('glob');

// Find all JSX files with GatsbyImage
const files = glob.sync('**/*.{js,jsx,tsx}');

files.forEach(file => {
  const content = fs.readFileSync(file, 'utf8');
  const updated = content.replace(
    /<GatsbyImage\s+image={(.+?)}\s+alt={(.+?)}\s*\/>/g,
    '<Image src={$1.url} alt={$2} width={800} height={600} />'
  );
  fs.writeFileSync(file, updated);
});

The script worked for simple cases but broke on complex images:

  • Art direction queries (different images for mobile/desktop)
  • Dynamic image sizes based on content type
  • Images with custom lazy loading logic
  • Background images rendered through CSS

Ended up manually converting 340 images. The remaining 507 we handled during the migration as we encountered them. Three months later, we're still finding broken images on obscure pages.

The lesson: Image migration is never simple. Budget extra time and start early. Consider keeping some Gatsby-style automatic optimization if Next.js manual config is overwhelming.

The Build Pipeline Integration Hell

Our Gatsby deployment was perfectly optimized for Netlify:

  • Automatic branch previews
  • Form handling built-in
  • Edge function integration
  • Build plugin ecosystem

Moving to Vercel for Next.js should have been smooth. Both platforms are built for their respective frameworks. Instead, it was weeks of configuration hell.

Environment variables: Gatsby used GATSBY_ prefix for public variables. Next.js uses NEXT_PUBLIC_. Our deployment had 23 environment variables that needed prefixes changed and values updated.

Build commands: Gatsby's build process was gatsby build. Next.js needed next build && next export for static deployment. Our CI pipeline assumed specific output directories.

Asset optimization: Netlify's asset optimization conflicted with Next.js built-in optimization. Images were being double-optimized, causing quality loss and slower load times.

Domain configuration: Moving from site.netlify.app to site.vercel.app broke all our staging links in documentation, QA tools, and client communications.

Form handling: Netlify forms worked with simple HTML. Vercel requires API routes. Had to rebuild our contact form, newsletter signup, and customer support forms.

The deployment pipeline took 2 weeks to stabilize. We had working code that couldn't deploy properly. Management started questioning the decision when launches kept failing.

The lesson: Factor in platform migration costs alongside framework migration. Different hosting providers have different optimization strategies that may conflict.

What I'd Do Differently Next Time

Start with a Smaller Scope

We migrated everything at once - 4,200 pages, 23 GraphQL queries, 12 plugins, complex data relationships. Should have started with a smaller section:

Phase 1: Blog (100 pages, simple content model)
Phase 2: Product pages (4,000 pages, complex but repetitive)
Phase 3: Marketing pages (100 pages, custom layouts)

Smaller scope means faster feedback loops and easier rollback if things go wrong.

Build Migration Tools Early

Spent too much time on manual conversion. Should have invested in better automation:

  • Query converter: Turn GraphQL queries into fetch calls automatically
  • Component mapper: Map Gatsby-specific components to Next.js equivalents
  • Asset processor: Batch convert image configurations
  • URL validator: Check that all internal links work after migration

Writing these tools upfront would have saved 40+ hours of repetitive work.

Set Up Monitoring Before Launch

We deployed first, then configured error tracking. Should have been the opposite:

  • Error tracking: Sentry configured with Next.js source maps
  • Performance monitoring: Real User Monitoring for page loads
  • Build monitoring: Slack alerts for deployment failures
  • SEO tracking: Google Search Console configured for new URLs

Monitoring helps you catch issues immediately instead of hearing about them from users.

Plan for Two-Way Sync During Migration

Content kept changing while we were migrating. Sarah's team couldn't stop publishing for 4 weeks. We ended up with:

  • Content drift: Staging had old content, production had new updates
  • Double work: Changes made to Gatsby that needed replication in Next.js
  • Launch delays: Had to sync final content right before cutover

Better approach: Set up content freezes or automated sync between CMS and both frameworks during migration window.

Budget for Post-Launch Maintenance

Migration doesn't end at deployment. The real work starts when users find edge cases:

  • Week 1: Fix production-only issues (different caching, environment variables)
  • Week 2: Mobile-specific bugs (iOS Safari is always special)
  • Week 3: SEO and search optimization (Google re-crawling takes time)
  • Week 4: Performance tuning (real usage patterns reveal bottlenecks)

Plan for 50% additional time after "launch" to handle stabilization.

The migration was ultimately successful - builds went from 47 minutes to 4 minutes, memory usage dropped 75%, deployment success rate hit 97%. But it took 6 weeks total instead of the planned 4 weeks, and we learned every lesson the hard way.

Next time I'll be smarter about scope, tooling, and expectations. The framework migration itself is only half the work - integration, testing, and stabilization are just as important.

When Migration Isn't an Option

Not every team can migrate away from Gatsby immediately. Budget constraints, technical debt, or resource limitations mean some sites will stay on Gatsby longer than anyone wants. If you're stuck maintaining a Gatsby site while migration approval is pending, the survival strategies in the next guide will help you keep things running without losing your sanity.

Migration Resources That Actually Help

Related Tools & Recommendations

tool
Similar content

Webpack: The Build Tool You'll Love to Hate & Still Use in 2025

Explore Webpack, the JavaScript build tool. Understand its powerful features, module system, and why it remains a core part of modern web development workflows.

Webpack
/tool/webpack/overview
100%
tool
Similar content

When Gatsby Still Works Well in 2025: Use Cases & Successes

Yeah, it has problems, but here's when it's still your best bet

Gatsby
/tool/gatsby/when-gatsby-works-well
92%
compare
Recommended

Framework Wars Survivor Guide: Next.js, Nuxt, SvelteKit, Remix vs Gatsby

18 months in Gatsby hell, 6 months testing everything else - here's what actually works for enterprise teams

Next.js
/compare/nextjs/nuxt/sveltekit/remix/gatsby/enterprise-team-scaling
88%
tool
Similar content

Migrate from Create React App to Vite & Next.js: A Practical Guide

Stop suffering with 30-second dev server startup. Here's how to migrate to tools that don't make you want to quit programming.

Create React App
/tool/create-react-app/migration-guide
82%
tool
Similar content

Fix Slow Gatsby Builds: Boost Performance & Prevent Crashes

Turn 47-minute nightmares into bearable 6-minute builds while you plan your escape

Gatsby
/tool/gatsby/fixing-build-performance
70%
integration
Recommended

I Spent Two Weekends Getting Supabase Auth Working with Next.js 13+

Here's what actually works (and what will break your app)

Supabase
/integration/supabase-nextjs/server-side-auth-guide
60%
tool
Recommended

Next.js - React Without the Webpack Hell

competes with Next.js

Next.js
/tool/nextjs/overview
60%
tool
Similar content

Create React App is Dead: Why & How to Migrate Away in 2025

React team finally deprecated it in 2025 after years of minimal maintenance. Here's how to escape if you're still trapped.

Create React App
/tool/create-react-app/overview
55%
tool
Recommended

Astro - Static Sites That Don't Suck

competes with Astro

Astro
/tool/astro/overview
55%
tool
Recommended

Fix Astro Production Deployment Nightmares

competes with Astro

Astro
/tool/astro/production-deployment-troubleshooting
55%
compare
Recommended

Which Static Site Generator Won't Make You Hate Your Life

Just use fucking Astro. Next.js if you actually need server shit. Gatsby is dead - seriously, stop asking.

Astro
/compare/astro/nextjs/gatsby/static-generation-performance-benchmark
55%
tool
Similar content

JetBrains WebStorm Overview: Is This JavaScript IDE Worth It?

Explore JetBrains WebStorm, the powerful JavaScript IDE for React and web development. Discover its features, compare it to VS Code, and find out if it's worth

WebStorm
/tool/webstorm/overview
50%
alternatives
Recommended

GitHub Actions Alternatives That Don't Suck

integrates with GitHub Actions

GitHub Actions
/alternatives/github-actions/use-case-driven-selection
50%
tool
Recommended

GitHub Actions Security Hardening - Prevent Supply Chain Attacks

integrates with GitHub Actions

GitHub Actions
/tool/github-actions/security-hardening
50%
alternatives
Recommended

Tired of GitHub Actions Eating Your Budget? Here's Where Teams Are Actually Going

integrates with GitHub Actions

GitHub Actions
/alternatives/github-actions/migration-ready-alternatives
50%
tool
Similar content

Solana Web3.js v1.x to v2.0 Migration: A Comprehensive Guide

Navigate the Solana Web3.js v1.x to v2.0 migration with this comprehensive guide. Learn common pitfalls, environment setup, Node.js requirements, and troublesho

Solana Web3.js
/tool/solana-web3js/v1x-to-v2-migration-guide
45%
tool
Similar content

Node.js ESM Migration: Upgrade CommonJS to ES Modules Safely

How to migrate from CommonJS to ESM without your production apps shitting the bed

Node.js
/tool/node.js/modern-javascript-migration
45%
tool
Similar content

Shopify App Bridge Overview: JavaScript SDK for Embedded Apps

Explore Shopify App Bridge, the official JavaScript SDK for embedded apps. Understand its core features, developer experience, and common gotchas to build robus

Shopify App Bridge
/tool/shopify-app-bridge/overview
45%
compare
Similar content

Zed vs VS Code: Why I Switched After 7GB RAM Bloat

My laptop was dying just from opening React files

Zed
/compare/visual-studio-code/zed/developer-migration-guide
40%
tool
Similar content

Truffle is Dead: Smart Contract Migration & Alternatives

Explore why the Truffle framework was discontinued, its role in smart contract development, and essential migration options and alternatives for your decentrali

Truffle Suite
/tool/truffle/overview
38%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization