A client came to me last year with a Squarespace site that was pulling in about 4,000 organic visits a month. Good content, decent rankings. They wanted to move to WordPress for more control, better plugin options, and a developer who could actually get inside the code. Their concern was simple: “We can’t lose those rankings. Can you guarantee we won’t?”
Nobody can guarantee rankings through a migration. Anyone who tells you otherwise is lying. What you can do is follow a process that gives Google every signal it needs to understand that your content moved, not disappeared. That’s what this guide covers — the full technical process of migrating a site to WordPress without wrecking your SEO.
This is a long one. I’ve covered the full process from pre-migration planning through launch-day checks because the mistakes people make happen at every stage, not just during the import itself.
Which Platforms Are We Talking About?
The migration process changes depending on where you’re starting from. Here’s a quick breakdown of the common platforms I work with and what makes each one interesting:
- Squarespace: Exports a limited XML file. Images are hosted on Squarespace CDN and need to be downloaded and re-uploaded separately. URL structures are usually clean.
- Wix: No native export. You’re manually exporting content or scraping. Wix URLs are notoriously messy and often include query strings or hash fragments.
- Joomla: Has a database you can work with directly. Articles, categories, and user data are all accessible via SQL. The biggest issue is usually the URL structure diverging from what WordPress generates.
- Drupal: Also database-driven. Drupal’s node system maps reasonably well to WordPress posts. The tricky part is custom field data and taxonomy structures.
- Custom PHP CMS: Every build is different. Sometimes there’s a clean database with posts and categories; sometimes content is hardcoded in PHP files. You have to assess each one individually.
Regardless of the platform, the migration follows the same phases: planning, content extraction, WordPress import, redirect setup, SEO preservation, and testing.
Phase 1: Pre-Migration Planning
Content Audit First, Migration Second
Don’t migrate everything blindly. Before touching a single file, crawl the existing site with a tool like Screaming Frog or Sitebulb and export every URL with its title, meta description, and response code. This gives you three things: a map of what exists, a list of what’s actually ranking, and a record you can compare against after launch.
While you’re at it, check Google Search Console. Export the top-performing pages by clicks and impressions for the past 12 months. These are the URLs you cannot afford to lose or break. Flag them. They get extra attention during redirect mapping.
URL Mapping: The Most Important Spreadsheet You’ll Build
Open a spreadsheet with two columns: old URL and new URL. Map every page. If the old URL was /about-us/our-team and the new WordPress URL will be /our-team/, write that down. Every. Single. Page.
For sites with hundreds of pages, you can often generate this mapping programmatically by querying the old database and the new WordPress database side by side, matching on slugs or titles. But always do a manual review of the top 50 pages by traffic. Automation misses edge cases — duplicate slugs, special characters in titles, pages that were redirected on the old site but still get traffic.
Baseline SEO Snapshot
Before you go live, record:
- Current rankings for your 20–30 most important keywords (use Ahrefs, SEMrush, or even a manual check)
- Domain Authority / Domain Rating
- Total indexed pages (check
site:yourdomain.comin Google) - Core Web Vitals scores from PageSpeed Insights
- Backlink profile export from Ahrefs or Moz
You need this before migration so you have a real before/after comparison. Two months after launch, when you’re checking whether rankings recovered, you’ll be glad you have it.
Phase 2: Setting Up the WordPress Environment
Do everything on a staging environment first. Never build a migration directly on the live domain — you’ll be running imports, tweaking database records, and testing redirects, and none of that should be visible to visitors or Google mid-process.
Set up WordPress on a subdomain like staging.yourdomain.com or use a local environment (I use LocalWP for local development). Install the theme, essential plugins, and configure the permalink structure before you import any content. Changing the permalink structure after importing content means regenerating slugs and invalidating any redirect mappings you’ve already built.
Permalink structure recommendation: use /%postname%/ for most sites. It’s clean, readable, and matches what most platforms use. If the old site used a date-based structure like /2021/03/post-title/ and those URLs have backlinks, keep the date structure in WordPress to avoid needing redirects for every single post.
Phase 3: Content Migration
Squarespace to WordPress
Squarespace gives you an export under Settings > Advanced > Import/Export. It produces a WordPress-compatible XML file. Use the WordPress Importer plugin (Tools > Import) to bring it in. Images won’t import correctly — Squarespace hosts them on their CDN, and those URLs will break once you cancel your Squarespace subscription.
After importing the XML, run this WP-CLI command to attempt downloading the remote images:
wp media import https://images.squarespace-cdn.com/... --post_id=42 --featured_image
For bulk image handling across all posts, a better approach is to use a plugin like “Auto Upload Images” immediately after import. It scans post content, finds external image URLs, downloads them to your media library, and updates the post content. After it runs, audit a sample of 20–30 posts to confirm images loaded correctly.
Wix to WordPress
Wix has no export. Your options are manual copy-paste (viable for small sites), scraping with a tool like HTTrack or a custom Python script, or using a paid migration service. For sites with more than 30–40 pages, I write a scraper that hits each page, extracts the main content area, and builds a CSV or JSON file that I then import into WordPress programmatically.
The programmatic import uses wp_insert_post():
<?php
$posts = json_decode( file_get_contents( 'wix-content.json' ), true );
foreach ( $posts as $post_data ) {
$post_id = wp_insert_post( [
'post_title' => sanitize_text_field( $post_data['title'] ),
'post_content' => wp_kses_post( $post_data['body'] ),
'post_status' => 'publish',
'post_type' => 'post',
'post_name' => sanitize_title( $post_data['slug'] ),
'post_date' => $post_data['published_date'],
] );
if ( $post_data['meta_description'] ) {
update_post_meta( $post_id, '_yoast_wpseo_metadesc', $post_data['meta_description'] );
}
}
?>
Run this as a one-time script via WP-CLI: wp eval-file import-wix.php. Check the results in the WordPress admin and spot-check 10–15 posts before proceeding.
Joomla and Drupal to WordPress
Both platforms store content in MySQL databases, which means you can work with the data directly. For Joomla, content lives in jos_content. For Drupal, it’s spread across node, field_data_body, and associated tables depending on the version.
The FG Joomla to WordPress and FG Drupal to WordPress plugins handle most of the heavy lifting. Install the plugin, point it at the old database credentials, and it pulls everything across. After the automated import, always do a SQL-level audit on the WordPress side to check for encoding issues, orphaned metadata, and malformed content.
A common issue with Joomla imports: the {loadmodule} and {loadposition} shortcodes that Joomla uses in content don’t translate to WordPress. You need a SQL find-and-replace to strip or convert them:
UPDATE wp_posts
SET post_content = REPLACE(post_content, '{loadmodule mod_custom}', '')
WHERE post_content LIKE '%{loadmodule%';
Run a few of these before reviewing manually, because regex in SQL is limited and you’ll miss variations. Spot-check after every batch update.
Custom PHP CMS
These require the most assessment time. I typically spend the first hour just mapping the database schema — what tables exist, how content relates to categories and users, what’s stored as serialized data versus plain text. Once I understand the structure, I write a migration script that queries the old database and inserts into WordPress using wp_insert_post() and wp_insert_term(). If the old site has custom fields, they map to WordPress post meta via update_post_meta().
The biggest trap with custom CMS migrations is assuming the data is clean. I’ve seen content databases where some posts have HTML in the title field, where dates are stored as Unix timestamps in one table and MySQL datetime in another, and where the “published” flag uses 1/0 in some records and ‘yes’/’no’ in others. Always sanitize and validate before inserting into WordPress.
The Migration That Almost Went Sideways
A Drupal 7 site, roughly 600 published nodes, 8 years of content. The client’s main concern was their blog — several posts were getting consistent organic traffic and had links from industry publications. We used the FG Drupal to WordPress plugin for the initial import, which went cleanly. Redirects were set up, the staging environment looked good, the client signed off.
We switched DNS over on a Friday afternoon. By Saturday morning, the client messaged saying their homepage was returning a 404 in Google Search Console — not the blog posts, the homepage.
Here’s what happened: the Drupal site had a node set as the front page (node/1). During migration, the plugin imported it as a regular post with the slug home. WordPress was serving a different page as the front page via Settings > Reading. But the old Drupal site had canonical URLs pointing to /node/1 in some internal links, and the redirect for /node/1 was missing from the .htaccess file because we’d only mapped named URLs, not node IDs.
The fix was a 30-second .htaccess addition and a Search Console recrawl request. No lasting damage. But it’s exactly the kind of edge case that pre-migration audits are supposed to catch — and this one slipped through because we focused on slug-based URLs and forgot Drupal also exposes content at numeric node paths. Now I always add a Drupal node-ID redirect pass as a standard step:
RewriteRule ^node/([0-9]+)$ /new-slug-here/ [R=301,L]
For sites with many nodes, generate these rules programmatically from the old database before going live.
Phase 4: Setting Up 301 Redirects
This is the most important technical step for SEO preservation. Every URL that changes needs a 301 redirect pointing from the old location to the new one. Miss this and you lose the link equity those pages have accumulated over years.
On Apache servers (the most common setup for WordPress hosting), redirects go in .htaccess:
RewriteEngine On
# Redirect old Squarespace blog URLs
RewriteRule ^blog/my-old-post-title/?$ /my-old-post-title/ [R=301,L]
RewriteRule ^blog/another-post/?$ /another-post/ [R=301,L]
# Redirect old category structure
RewriteRule ^category/services/web-design/?$ /web-design/ [R=301,L]
On Nginx, the equivalent block looks like this:
location = /blog/my-old-post-title/ {
return 301 /my-old-post-title/;
}
location ~* ^/category/(.*)$ {
return 301 /$1;
}
For WordPress specifically, you can also manage redirects through a plugin like Redirection or Rank Math’s redirect module. I prefer .htaccess or Nginx rules for permanent redirects because they’re faster (no PHP execution) and survive plugin changes. Use the plugin as a management interface but export the rules to server config for production.
Testing Your Redirects
Before going live, test every redirect in the mapping spreadsheet. Use curl from the command line to verify:
curl -I https://staging.yourdomain.com/old-url/
You’re looking for HTTP/1.1 301 Moved Permanently and a Location: header pointing to the correct new URL. If you get a 200 on the old URL, the redirect isn’t in place. If you get a 302, change it to 301 — temporary redirects don’t pass link equity.
For large redirect sets, Screaming Frog can batch-test redirects. Upload the old URL list, crawl it, and check the response codes column.
Phase 5: Preserving SEO Rankings
People worry about losing rankings during a migration. The worry is legitimate — poorly executed migrations absolutely can tank rankings. But a clean migration, done correctly, typically sees a temporary dip of 2–4 weeks followed by recovery to baseline. Here’s how to minimize that dip.
Keep Your Meta Data
Every page’s title tag and meta description needs to transfer. If you’re using Yoast SEO, import your existing meta data into Yoast’s fields during the content migration. When using wp_insert_post(), follow it immediately with:
update_post_meta( $post_id, '_yoast_wpseo_title', $old_meta_title );
update_post_meta( $post_id, '_yoast_wpseo_metadesc', $old_meta_description );
Don’t let WordPress auto-generate titles from the post title if the original had a custom, optimized title tag. Check this manually for your top 20 pages after import.
XML Sitemap Submission
After launch, submit your new XML sitemap to Google Search Console immediately. This tells Google exactly what URLs exist on the new site and prompts faster recrawling. Make sure the sitemap URL itself didn’t change — if your old site had a sitemap at /sitemap.xml, WordPress (with Yoast or Rank Math) will also serve it at /sitemap.xml, so no redirect needed there.
Canonical Tags
Verify canonical tags are pointing to the correct URLs on the new site. A common issue: WordPress canonical tags include a trailing slash, but the old site didn’t. This causes self-referencing canonicals to not match exactly, which is usually fine, but worth verifying. If you’re doing a domain change at the same time as a CMS migration — say, moving from old-domain.com to new-domain.com — use the Google Search Console Change of Address tool after launch.
Page Speed and Core Web Vitals
WordPress out of the box is slower than a static Squarespace or Wix site. If you move to WordPress and your Core Web Vitals drop significantly, that’s a rankings factor. Before launch, set up:
- A caching plugin (WP Rocket or W3 Total Cache)
- Image optimization (ShortPixel or Imagify)
- A CDN (Cloudflare is free and effective)
- Lazy loading for images (enabled by default in WordPress 5.5+)
Run PageSpeed Insights on the staging site before going live and fix any obvious issues. A First Contentful Paint over 3 seconds is a problem. Fix it before the migration, not after.
Phase 6: Launch and Post-Launch Monitoring
The Launch Sequence
- Confirm all redirects are in place and tested on staging.
- Do a final content audit — spot-check 30+ pages on staging.
- Take a full database backup of the completed WordPress staging site.
- Put the old site in maintenance mode (optional but prevents new content being added during the switch).
- Point DNS to the new host. TTL changes can take 24–48 hours to propagate fully.
- Once DNS propagates, verify the live site is loading correctly.
- Submit the XML sitemap in Google Search Console.
- Use Search Console’s URL Inspection tool to request indexing on the 10 most important pages.
- Set a reminder to check rankings and Search Console coverage reports in 2 weeks and 6 weeks.
What to Watch For
In Search Console, watch the Coverage report for spikes in 404 errors. These indicate URLs that are getting traffic or crawl budget that don’t have a redirect. Add redirects for any 404s that show meaningful impressions.
Watch the Performance report for drops in clicks or impressions compared to the baseline you captured before migration. Some fluctuation is normal in the first 2–4 weeks. A sustained drop beyond 6 weeks warrants investigation — look at which specific pages dropped and whether their redirects are working correctly.
Check backlinks using Ahrefs or similar. If any high-value backlinks are pointing to a URL that’s now 404ing, that’s a missed redirect. Add it immediately.
Timeline and Cost Expectations
Every migration is different, but here’s a realistic framework:
- Small site (under 30 pages, Squarespace or Wix): 1–2 weeks, primarily manual work on content and redirects. Straightforward.
- Medium site (30–200 pages, Joomla, Drupal, or structured custom CMS): 2–4 weeks. More time spent on redirect mapping and post-import auditing.
- Large site (200+ pages, complex taxonomy, custom fields, e-commerce): 4–8+ weeks. Requires significant scripting, thorough testing, and a phased approach.
Cost scales with complexity. The content migration itself is often the smaller part of the budget — redirect mapping, SEO auditing, post-launch monitoring, and dealing with the inevitable edge cases take real time. For a medium site, expect 20–40 hours of developer work. Large migrations with e-commerce or custom post types can run 60–100+ hours.
Don’t cut corners on the planning phase to save money. The cost of a botched migration — in lost rankings, recovery time, and emergency fixes — far exceeds what proper planning costs upfront.
A Few Things People Get Wrong
Changing too much at once. If you’re migrating CMS and redesigning and changing your URL structure and switching domains all simultaneously, you have no idea which variable caused a rankings drop if one occurs. Migrate first, then redesign. Or redesign on the new platform before going live, but keep URLs identical to the old site.
Forgetting paginated URLs. If your old site had /blog/page/2/ style pagination and it was indexed, those need redirects too — ideally to the first page of the blog, not to 404s.
Not testing on mobile. Google indexes mobile-first. Test every template on mobile before launch. A desktop migration that breaks on mobile is a rankings problem.
Canceling the old hosting too fast. Keep the old site accessible (or at least the old database and files backed up) for at least 60 days after migration. You will need to reference old content, and you don’t want to be hunting through archives when something is missing.
Ready to Move Your Site to WordPress?
Migrations done right are not that risky. The risk comes from skipping steps, missing redirects, or rushing the launch because there’s a deadline. A methodical approach — audit, map, import, redirect, test, launch, monitor — handles 95% of what can go wrong.
I’ve migrated sites from Squarespace, Wix, Joomla, Drupal, and fully custom PHP codebases to WordPress. Each one teaches you something new, and I’ve built that experience into a process that protects rankings while giving clients the flexibility and control they’re moving to WordPress to get.
If you’re weighing a migration and want a technical assessment of your specific situation — platform, size, SEO stakes — get in touch. I’ll look at what you have and tell you exactly what the migration would involve, what risks exist, and how long it would realistically take. No fluff, just an honest technical read.