tva
← Insights

Fixing SEO After a WordPress to Astro Migration: Redirect Chains, Sitemaps, and GSC Cleanup

After migrating from WordPress to Astro, your site builds faster, loads faster, and costs less to host. But if your blog URLs changed during the migration — for example, from /post-slug/ to /insights/post-slug — you probably have SEO issues that only show up weeks later when Google finishes re-crawling your site.

Google Search Console will start sending emails about “Duplicate, Google chose different canonical than user” and “Not found (404) validation failed.” Your top-performing pages may be losing link equity through unnecessary redirect hops. And you might have two sitemaps competing with each other without knowing it.

This guide covers how to find and fix these issues. We ran into all of them on our own WordPress to Astro migration SEO cleanup and documented the fixes here.

What You’ll Need

  • SSH access to your server (nginx configuration)
  • Google Search Console access for your property
  • curl installed locally
  • Analytics tool (we use self-hosted Plausible)

What This Fixes

  • Multi-hop redirect chains (2+ redirects) reduced to single-hop 301s
  • Competing sitemaps consolidated to one auto-generated source
  • noindex pages removed from sitemap
  • Stale GSC properties and legacy WordPress sitemaps cleaned up
  • Canonical confusion between old and new URL structures

Step 1: Diagnose Redirect Chains

Check how many redirects your top URLs go through. If your WordPress URLs had trailing slashes and your Astro URLs don’t, you likely have a two-hop chain: one redirect strips the slash, another adds the new path prefix.

curl -sL -w "Hops: %{num_redirects}, Final: %{http_code}, URL: %{url_effective}\n" \
  -o /dev/null "https://yourdomain.com/old-post-slug/"

If the output shows Hops: 2, you have a redirect chain. Run this for your top five pages by traffic — those are the ones where lost link equity matters most.

Cross-reference with your analytics. If you see the same article appearing under multiple URL variants (with slash, without slash, with new prefix), your redirects are not consolidating traffic. We saw our top article split across three URL variants in Plausible, which confirmed the chain problem.

Step 2: Fix Nginx for Single-Hop Redirects

The standard nginx trailing-slash rule looks like this:

location ~ ^(.+)/$ { return 301 $1; }

This strips the trailing slash but does not know about your blog path change. The request then hits a second rule that redirects /slug to /insights/slug. Two hops.

Replace it with a blog-aware rule that checks if the slug matches a blog post and redirects directly to the final URL:

# Blog slugs with trailing slash — single-hop to /insights/
location ~ ^/([^/]+)/$ {
    set $slug $1;
    if (-f /usr/share/nginx/html/insights/$slug/index.html) {
        return 301 https://yourdomain.com/insights/$slug;
    }
    return 301 https://yourdomain.com/$slug;
}
# Multi-segment paths — just strip the slash
location ~ ^(.+)/$ { return 301 $1; }

The first rule handles root-level slugs (your old WordPress blog URLs). It checks if a matching page exists under /insights/ and redirects there directly. Non-blog paths fall through to the generic slash removal. After deploying, verify:

curl -sL -w "Hops: %{num_redirects}\n" -o /dev/null \
  "https://yourdomain.com/your-top-post-slug/"
# Expected: Hops: 1

Step 3: Consolidate Sitemaps

Check if you have more than one sitemap. A common pattern after migration: a static sitemap.xml you created manually during the move, plus an auto-generated sitemap-index.xml from Astro’s @astrojs/sitemap integration.

# Check both
curl -sI "https://yourdomain.com/sitemap.xml" | head -1
curl -sI "https://yourdomain.com/sitemap-index.xml" | head -1

If both return 200, you have competing sitemaps. Check which one robots.txt references:

curl -s "https://yourdomain.com/robots.txt" | grep -i sitemap

The fix: delete the static sitemap.xml from your public/ directory, update robots.txt to reference sitemap-index.xml, and let Astro’s integration handle everything. New posts will appear automatically on every build.

Also check the static sitemap for noindex pages. If your sitemap.xml lists pages like /legal or /privacy-policy that have <meta name="robots" content="noindex">, you are sending contradictory signals to Google. The auto-generated sitemap from @astrojs/sitemap can be configured with filter rules to exclude these pages correctly.

Step 4: Clean Up Google Search Console

Open GSC and check two things: properties and sitemaps.

Properties: If you have both a domain property (sc-domain:yourdomain.com) and a URL prefix property (https://www.yourdomain.com/), you are getting duplicate alerts for the same issues. Pick one. If your subdomains are separate projects, the URL prefix property is cleaner — it only reports on production traffic.

Sitemaps: Go to Indexing → Sitemaps. Remove any old WordPress sitemaps still registered there (post-sitemap.xml, page-sitemap.xml, sitemap-post-type-product.xml, etc.). Submit your new sitemap-index.xml as the only sitemap.

404 Validation: Go to Indexing → Pages → “Not found (404)” and check which URLs are listed. If they are old WordPress paths that no longer exist and should not exist (WooCommerce product pages, author pages, feed URLs), they will deindex over time. If they are blog posts that should redirect, your nginx rules need updating. After fixing the redirects, click “Validate Fix” to trigger a re-crawl.

Step 5: Verify Everything

Run these checks after deploying your fixes:

# 1. Redirect chains resolved
curl -sL -w "Hops: %{num_redirects}, URL: %{url_effective}\n" \
  -o /dev/null "https://yourdomain.com/your-top-post/"
# Expected: Hops: 1, URL ends in /insights/your-top-post

# 2. robots.txt points to generated sitemap
curl -s "https://yourdomain.com/robots.txt" | grep sitemap
# Expected: sitemap-index.xml

# 3. Old static sitemap returns 404
curl -sI "https://yourdomain.com/sitemap.xml" | head -1
# Expected: 404

# 4. Generated sitemap returns 200
curl -sI "https://yourdomain.com/sitemap-index.xml" | head -1
# Expected: 200

# 5. Non-www redirects to www
curl -sI -o /dev/null -w "%{http_code} %{redirect_url}\n" \
  "https://yourdomain.com/insights"
# Expected: 301 https://www.yourdomain.com/insights

How Long Until Google Catches Up

Single-hop redirects take effect immediately for new crawls. Sitemap changes are picked up within a few days. The canonical confusion — where Google prefers the old URL over the new one — takes longer to resolve. Google treats the <link rel="canonical"> tag as a hint, not a directive. If the old URL has more backlinks and historical click data, Google may keep choosing it as canonical for several weeks.

The combination of clean redirects, a single authoritative sitemap, and a consolidated GSC property gives Google the clearest possible signal. In our case, we saw the “Duplicate canonical” warnings start decreasing within two weeks after deploying these fixes.

We handle these kinds of migrations regularly as part of our WordPress performance and migration work. If you are dealing with post-migration SEO issues, get in touch.


Related Insights

Further Reading