How to Migrate Content from Shopify to Next.js Without Risking SEO
Most e-commerce businesses treat their Shopify store like a comfortable cage. They know it's restrictive, but they're scared to leave because of potential SEO drops and data loss. So they stick with Shopify, adding apps to fix scalability issues caused by the platform's architecture. They tweak themes while performance lags.
Migrating to Next.js isn't just a switch—it's a transformation. You're shifting from a monolithic, hosted e-commerce model to a flexible, high-performance framework that can go fully static or headless. This guide now includes expanded details on migrating your product database to a custom backend, such as PostgreSQL with Prisma, for those wanting to fully decouple from Shopify.
The fear of losing rankings is legitimate but manageable. With proper handling of URLs, metadata, and redirects, you can maintain—and often improve—your SEO. Google favors fast sites, and Next.js excels at speed.
This guide provides a zero-loss migration blueprint, prioritizing the MigrateCMS tool for content export, and incorporating best practices for product database migration.
Why Migrate from Shopify to Next.js?
You don't upgrade your store just for a new look. You do it because the platform can't scale with your vision.
- Performance Boost: Next.js leverages Static Site Generation (SSG) and Server-Side Rendering (SSR). No more database queries on every load—serve pre-rendered pages for instant experiences.
- Core Web Vitals Excellence: Shopify can struggle with LCP and CLS without heavy tweaks. Next.js optimizes these natively, improving Google rankings.
- Enhanced Security: A static or headless frontend reduces vulnerabilities. No direct database exposure.
- Developer Freedom: Escape Liquid templates. Build with React, modern tools, and true CI/CD.
- Cost Savings: Ditch premium Shopify plans. Host on Vercel or Netlify for scalable, affordable infrastructure.
- E-commerce Flexibility: Integrate with headless commerce backends or migrate fully to a custom database for complete control over products.
The risks—like broken links or lost product data—are real, but engineering mitigates them.
Step 1: Pre-Migration Audit and Planning
Don't touch code until you've mapped your store. Conduct a thorough audit.
1.1 Crawl Your Existing Shopify Site
Inventory everything to avoid missing assets. Tools: Screaming Frog, Ahrefs, SEMrush, or Shopify's export features.
The Inventory:
- All URLs (products, collections, pages, blogs).
- HTTP status codes (fix 404s pre-migration).
- Meta titles, descriptions.
- Canonical tags.
- Open Graph and structured data.
- Product data: SKUs, variants, images, inventory, metafields.
- Export to CSV/JSON. This is your migration manifest.
1.2 Analyze SEO and Overall Performance
Establish baselines.
- Track rankings via Google Search Console.
- Identify high-traffic pages/products—these get priority.
- Monitor Core Web Vitals and traffic in Google Analytics.
1.3 Backup Everything
Snapshot your Shopify store.
1.4 Plan Your Next.js Architecture
Shift from Shopify's structure to components.
- Rendering Strategy: SSG for static pages/blogs; SSR/ISR for dynamic products.
- Content Backend: Use MDX for static content; integrate a custom database for products if migrating fully.
- E-commerce Handling: For full migration, export products to a database (e.g., PostgreSQL via Prisma). For partial, keep Shopify as headless via Storefront API.
- Golden Rule: Match URL structures exactly (e.g.,
/products/my-productstays the same).
Step 2: Export Content from Shopify Using MigrateCMS
Automate exports—manual work is inefficient. MigrateCMS bridges Shopify to Next.js MDX for content, but for products, use Shopify exports and prepare for database import.
2.1 Set Up MigrateCMS
Access at https://migratecms.vercel.app/. It's browser-based—no installs needed.
- Export Shopify content (blogs, pages) as CSV.
- For products: Use Shopify Admin exports or API for CSV/JSON.
2.2 Export Process for Content
- Export blogs/pages from Shopify: Admin > Online Store > Blog posts/Pages > Export.
- Upload CSV to MigrateCMS.
- It converts to MDX, preserving slugs/handles for URLs.
- Strips Liquid tags for clean output.
- Generates frontmatter for metadata.
Example frontmatter from MigrateCMS export:
---
title: "My Awesome Post"
description: "This is the meta description from WordPress."
slug: "my-awesome-post"
date: "2024-03-25"
tags: ["seo", "migration"]
canonical: "[https://example.com/my-awesome-post](https://example.com/my-awesome-post)"
ogImage: "/images/my-post-og.jpg"
---2.3 Export Process for Products
- Download Shopify’s sample CSV templates from the Help Center.
- Export products via Admin > Products > Export (select all fields, including variants and metafields).
- Use Matrixify app for bulk exports if dealing with large catalogs or complex data.
- Clean the CSV: Remove duplicates, fix formatting issues (e.g., special characters), standardize columns (title, body_html, variant_sku, etc.).
- Export images separately and note URLs for migration to a CDN.
- For products, adapt to JSON if needed for import scripts; MigrateCMS can handle basic product pages, but for database migration, use CSV as source.
2.4 Why Use MigrateCMS for Shopify Migration?
- SEO Preservation: Automatically maps WordPress metadata to Next.js-compatible frontmatter.
- Efficiency: Batch exports save time over manual copying.
- Integrity: Maintains internal links and formatting.
Post-export, Store MDX files in your Next.js /content directory; store product CSV for database import.
Step 3: Set Up Your Next.js Project
3.1 Initialize Next.js
Use the latest Next.js (e.g., 16+ for improved React Server Components):
npx create-next-app@latest my-next-store --typescript --tailwind --eslint
cd my-next-store
npm run devUse App Router for modern features.
3.2 Install Dependencies
For MDX, database, and e-commerce:
npm install @next/mdx @mdx-js/react gray-matter prisma @prisma/client csv-parser
npx prisma initConfigure next.config.js. Tell Next.js how to handle the markdown files.
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
});
module.exports = withMDX({
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
});3.3 Set Up Database for Products (Prisma)
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Product {
id String @id @default(uuid())
title String
handle String @unique
description String?
vendor String?
productType String?
variants Variant[]
images Image[]
seoTitle String?
seoDesc String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Variant {
id String @id @default(uuid())
productId String
product Product @relation(fields: [productId], references: [id])
title String
price Float
sku String?
inventory Int?
}
model Image {
id String @id @default(uuid())
productId String
product Product @relation(fields: [productId], references: [id])
src String
alt String?
}Then run:
npx prisma migrate dev --name init3.4 3.4 Import Products to Database
Create a product seed script (prisma/seed.ts):
import { PrismaClient } from '@prisma/client';
import fs from 'fs';
import csv from 'csv-parser';
const prisma = new PrismaClient();
async function main() {
const products: any[] = [];
fs.createReadStream('products.csv')
.pipe(csv())
.on('data', (row) => products.push(row))
.on('end', async () => {
for (const row of products) {
await prisma.product.upsert({
where: { handle: row.Handle },
update: {},
create: {
title: row.Title,
handle: row.Handle,
description: row.Body,
vendor: row.Vendor,
productType: row.Type,
seoTitle: row.SEO_Title,
seoDesc: row.SEO_Description,
variants: {
create: {
title: row.Variant_Title || 'Default',
price: parseFloat(row.Variant_Price),
sku: row.Variant_SKU,
inventory: parseInt(row.Variant_Inventory_Qty) || 0,
},
},
// Add images similarly
},
});
}
console.log('Products imported!');
});
}
main().catch(e => console.error(e)).finally(async () => await prisma.$disconnect());Then run:
npx ts-node prisma/seed.ts3.5 Create Dynamic Routes
Use /app/products/[handle]/page.tsx for dynamic product pages.
Step 4: Import and Render Content
###4.1 Parse MDX Files (for Pages/Blogs)
Use gray-matter:
npm install gray-matterimport fs from 'fs';
import matter from 'gray-matter';
import { MDXRemote } from 'next-mdx-remote/rsc';
export async function getStaticProps({ params }: { params: { slug: string } }) {
const file = fs.readFileSync(`content/${params.slug}.mdx`, 'utf-8');
const { data: frontmatter, content } = matter(file);
return { props: { frontmatter, content } };
}
export default function Page({ frontmatter, content }: any) {
return (
<article>
<h1>{frontmatter.title}</h1>
<MDXRemote source={content} />
</article>
);
}4.2 Render Products from The New Database
In /app/products/[handle]/page.tsx:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function ProductPage({ params }: { params: { handle: string } }) {
const product = await prisma.product.findUnique({
where: { handle: params.handle },
include: { variants: true, images: true },
});
if (!product) return <div>Product not found</div>;
return (
<div>
<h1>{product.title}</h1>
<p>{product.description}</p>
{/* Render variants, images, etc. */}
</div>
);
}
export const revalidate = 3600; // ISR every hour4.3 Handle Images and Assets
- Export Shopify images via Admin.
- Migrate to CDN (e.g., Cloudinary).
- Update paths in database during import.
- Use Next.js
<Image>for optimization.
Test and re-test.
Step 5: Preserve URL Structures and Implement Redirects
5.1 Matching URLs
Map Shopify permalinks to Next.js routes (e.g., /products/[handle]).
5.2 Set Up 301 Redirects
In next.config.js:
module.exports = {
async redirects() {
return [
{
source: '/old-path/:slug',
destination: '/products/:slug',
permanent: true,
},
];
},
};Always test with A Redirect Checker.
Step 6: Handle Metadata and Structured Data
Google doesn't read your content like a human. It reads the metadata.
6.1 Dynamic Metadata
import { Metadata } from 'next';
export async function generateMetadata({ params }: { params: { handle: string } }): Promise<Metadata> {
const product = await getProduct(params.handle); // From Prisma
return {
title: product.seoTitle || product.title,
description: product.seoDesc,
openGraph: { images: product.images[0]?.src },
alternates: { canonical: `/products/${params.handle}` },
};
}6.2 Structured Data Schema
Add JSON-LD for products/blogs:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Product",
"name": product.title,
// ... more fields
}) }} />6.3 Robots.txt and Sitemap
robots.txt: Tell them what to scan.sitemap.xml: Give them the list. Usenext-sitemapto auto-generate this on build.
npm install next-sitemapConfigure to include all URLs from your audit.
Step 7: Optimize for Performance and SEO
- Use
Next.js Imagefor product image optimization. - Implement lazy loading.
- Enable ISR for dynamic content.
- Ensure accessibility for good Lighthouse scores and better SEO.
- For large product catalogs, use pagination and infinite scroll.
Step 8: Testing the Migration
8.1 Local Testing
npm run build && npm startValidate renders, product details, carts (integrate Stripe for payments if needed).
Validate URLs, metadata, and renders.
8.2 SEO Validation
Recrawl with tools; check Google Search Console (GSC).
8.3 Staging Environment
Deploy to staging; test end-to-end, including product searches and variants.
Step 9: Deployment and Go-Live
- Deploy to Vercel/Netlify.
- Update DNS to point to new host.
- Submit new sitemap to Google Search Console.
- Monitor for 404s and fix with redirects.
Step 10: Post-Migration Monitoring and Optimization
- Track with Ahrefs/SEMrush.
- Fix issues promptly.
- Leverage Next.js and/or feature flags for A/B tests.
- Monitor database performance; scale as needed.
Migration from Shopify to Next.js: The Last Word
You can have modern flexibility without sacrificing SEO. By auditing thoroughly, using MigrateCMS for content, exporting and importing products to a custom database, and setting strict redirects, you secure your future while honoring your past.
Ready? *Start with MigrateCMS, export data and products via CSV backup. Troubleshoot as needed—that's engineering.