Migrating from WordPress to a headless CMS represents a fundamental architectural shift that requires careful planning to preserve years of SEO work. This guide covers the technical implementation details for maintaining search rankings during your WordPress migration.
Pre-Migration SEO Audit and Planning
Before touching any code, document your current SEO landscape. Export all URLs, meta titles, descriptions, and structured data from your WordPress site.
// WordPress SQL query to export all posts with SEO data
SELECT
p.post_name as slug,
p.post_title,
p.post_content,
p.post_excerpt,
pm_seo_title.meta_value as seo_title,
pm_seo_desc.meta_value as seo_description,
p.post_date
FROM wp_posts p
LEFT JOIN wp_postmeta pm_seo_title ON p.ID = pm_seo_title.post_id
AND pm_seo_title.meta_key = '_yoast_wpseo_title'
LEFT JOIN wp_postmeta pm_seo_desc ON p.ID = pm_seo_desc.post_id
AND pm_seo_desc.meta_key = '_yoast_wpseo_metadesc'
WHERE p.post_status = 'publish'
AND p.post_type IN ('post', 'page')
ORDER BY p.post_date DESC;
Use tools like Screaming Frog or Sitemap Generators to crawl your existing site and create a comprehensive URL inventory. This becomes your redirect mapping foundation.
Content Export and Data Migration
WordPress content export goes beyond the built-in export tool. You need to preserve custom fields, taxonomies, and media associations.
Advanced WordPress Export Script
['post', 'page'],
'post_status' => 'publish',
'numberposts' => -1
]);
$export_data = [];
foreach ($posts as $post) {
$custom_fields = get_post_meta($post->ID);
$terms = wp_get_post_terms($post->ID, get_object_taxonomies($post->post_type));
$export_data[] = [
'id' => $post->ID,
'title' => $post->post_title,
'slug' => $post->post_name,
'content' => apply_filters('the_content', $post->post_content),
'excerpt' => $post->post_excerpt,
'date' => $post->post_date,
'modified' => $post->post_modified,
'type' => $post->post_type,
'meta' => $custom_fields,
'terms' => $terms,
'featured_image' => get_post_thumbnail_id($post->ID)
];
}
file_put_contents('wordpress-export.json', json_encode($export_data, JSON_PRETTY_PRINT));
}
export_content_for_headless();
Image and Media Migration
Media files require special attention during headless CMS migration. Plan your CDN strategy early.
// Node.js script to migrate WordPress media to cloud storage
const AWS = require('aws-sdk');
const fs = require('fs');
const path = require('path');
const s3 = new AWS.S3();
async function migrateMedia(exportData) {
for (const post of exportData) {
// Extract image URLs from content
const imageRegex = /
]+src\s*=\s*['"]([^'"]+)['"]/g;
let match;
while ((match = imageRegex.exec(post.content)) !== null) {
const imageUrl = match[1];
if (imageUrl.includes('/wp-content/uploads/')) {
await uploadToS3(imageUrl, post.slug);
}
}
}
}
async function uploadToS3(imageUrl, postSlug) {
const fileName = path.basename(imageUrl);
const response = await fetch(imageUrl);
const buffer = await response.buffer();
const uploadParams = {
Bucket: 'your-bucket',
Key: `media/${postSlug}/${fileName}`,
Body: buffer,
ContentType: response.headers.get('content-type')
};
return s3.upload(uploadParams).promise();
}
URL Structure and Redirect Implementation
Maintaining URL structure is critical for SEO preservation. If changing URL patterns, implement 301 redirects at the server level for maximum efficiency.
Cloudflare Workers Redirect Implementation
// Cloudflare Workers script for handling WordPress redirects
const REDIRECT_MAP = {
'/old-wordpress-post/': '/new-headless-post/',
'/category/tech/': '/topics/technology/',
'/author/john-doe/': '/writers/john-doe/'
};
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
const pathname = url.pathname;
// Check for exact match redirects
if (REDIRECT_MAP[pathname]) {
return Response.redirect(url.origin + REDIRECT_MAP[pathname], 301);
}
// Pattern-based redirects for WordPress structure
const wpPostMatch = pathname.match(/^\/(\d{4})\/(\d{2})\/(\d{2})\/([^/]+)\/?$/);
if (wpPostMatch) {
const slug = wpPostMatch[4];
return Response.redirect(`${url.origin}/blog/${slug}/`, 301);
}
// Continue to origin if no redirect needed
return fetch(request);
}
Nginx Redirect Configuration
For server-level redirects, Nginx provides efficient pattern matching:
# nginx.conf redirects for WordPress migration
server {
# Redirect WordPress date-based URLs
location ~* ^/(\d{4})/(\d{2})/(\d{2})/([^/]+)/?$ {
return 301 /blog/$4/;
}
# Redirect WordPress categories
location ~* ^/category/(.+)/?$ {
return 301 /topics/$1/;
}
# Redirect WordPress tags
location ~* ^/tag/(.+)/?$ {
return 301 /tags/$1/;
}
# Redirect feed URLs
location = /feed/ {
return 301 /rss.xml;
}
location = /comments/feed/ {
return 301 /rss.xml;
}
}
Metadata and Structured Data Preservation
SEO metadata requires careful mapping between WordPress fields and your headless CMS structure.
Meta Tag Implementation in Headless Frontend
// React component for SEO meta tags
import Head from 'next/head';
export function SEOHead({ post, site }) {
const title = post.seoTitle || post.title;
const description = post.seoDescription || post.excerpt;
const canonicalUrl = `${site.url}/${post.slug}/`;
return (
{title}
{/* Open Graph tags */}
{post.featuredImage && (
)}
{/* Twitter Card tags */}
{/* Structured data */}
);
}
XML Sitemap Generation
Generate XML sitemaps programmatically from your headless CMS data:
// Next.js API route for dynamic sitemap generation
export default async function handler(req, res) {
const posts = await fetchAllPublishedPosts();
const pages = await fetchAllPages();
const sitemap = `
${pages.map(page => `
https://yoursite.com/${page.slug}/
${page.updatedAt}
0.8
`).join('')}
${posts.map(post => `
https://yoursite.com/blog/${post.slug}/
${post.updatedAt}
0.6
`).join('')}
`;
res.setHeader('Content-Type', 'text/xml');
res.write(sitemap);
res.end();
}
Performance Optimization for SEO
Headless architecture enables significant performance improvements that boost SEO rankings.
Static Generation with ISR
// Next.js ISR implementation for blog posts
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug);
if (!post) {
return { notFound: true };
}
return {
props: { post },
revalidate: 3600, // Revalidate every hour
};
}
export async function getStaticPaths() {
const posts = await fetchAllPosts();
return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
fallback: 'blocking'
};
}
Testing and Validation
Before going live, validate your migration with comprehensive testing:
- Redirect Testing: Use tools like Redirect Mapper to verify all redirects work correctly
- Metadata Validation: Check meta tags with Facebook Debugger and Twitter Card Validator
- Structured Data: Validate JSON-LD with Google's Rich Results Test
- Performance Testing: Run Lighthouse audits to ensure improved Core Web Vitals
- Crawl Testing: Use Google Search Console's URL Inspection tool
Post-Migration Monitoring
Monitor your rankings closely for the first month after migration. Set up alerts for:
- 404 errors in Google Search Console
- Ranking drops for key pages
- Crawl errors or indexing issues
- Core Web Vitals changes
WordPress to headless CMS migration requires meticulous planning, but the SEO benefits—improved performance, better user experience, and enhanced scalability—justify the effort. Focus on preserving URL structure, maintaining metadata integrity, and implementing proper redirects to ensure a smooth transition that preserves your hard-earned search rankings.