Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

How I replace Yoast SEO when I move a client from WordPress to Astro

When I pull a client off WordPress, Yoast (or Rank Math) is usually one of the first plugins they ask about. They spent years tweaking titles and meta descriptions. They dont want that work thrown out with the old theme.

When I pull a client off WordPress, Yoast (or Rank Math) is usually one of the first plugins they ask about. They spent years tweaking titles and meta descriptions. They dont want that work thrown out with the old theme.

Here is the checklist I actually run so rankings dont fall off a cliff during the move.

1. Export the SEO data before you touch the theme

I never migrate "by feel". I pull a CSV of every public URL with:

  • post/page title
  • Yoast SEO title (or Rank Math equivalent)
  • meta description
  • canonical if they set one
  • noindex / nofollow flags
  • focus keyphrase (nice to have, not required)

If a plugin export is messy I just query wp_postmeta for _yoast_wpseo_title and _yoast_wpseo_metadesc and join it to the posts table. Ugly SQL, works every time.

Save that file. It becomes your source of truth for the Astro site.

2. Put titles and descriptions next to the content

On Astro I keep SEO fields in frontmatter (or a seo object on each content entry), not buried in a layout:

---
title: "Emergency plumber in Surry Hills"
description: "Same-day plumbing in Surry Hills. Fixed pricing, no call-out fee."
canonical: "/emergency-plumber-surry-hills/"
noindex: false
---

For pages that used to live in WordPress custom fields or ACF, I put the same shape in /data/*.json so the client can still tweak copy later through the /edit flow without opening a repo.

One rule: the Astro title in frontmatter is the browser title. If Yoast used a seperate SEO title template, map that into seoTitle and prefer it in the layout. Dont silently overwrite the H1.

3. One layout owns the head tags

I wire a single BaseLayout.astro (or equivalent) that always emits:

  • <title>
  • <meta name="description">
  • Open Graph title, description, image, url
  • Twitter card tags
  • canonical link
  • robots when noindex is true

Something like:

---
const pageTitle = Astro.props.seoTitle || Astro.props.title;
const desc = Astro.props.description || "";
const canonical = new URL(Astro.props.canonical || Astro.url.pathname, Astro.site);
---
<title>{pageTitle}</title>
<meta name="description" content={desc} />
<link rel="canonical" href={canonical} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={desc} />
<meta property="og:url" content={canonical} />

Keep OG image defaults sane. A lot of small business sites had Yoast pulling a random featured image. I set one default brand OG image and only override when the page has a real hero.

4. Match canonicals to your redirect map

This is where people lose rankings. If /services/plumbing/ used to be the URL and your Astro route is /plumbing/, the redirect map and the canonical must agree.

I build the redirect CSV first (old URL → new URL), then set each page's canonical to the new URL. Old URLs 301. New URLs self-canonical. No surprises.

If Yoast had odd trailing-slash rules, pick one convention in Astro and stick to it everywhere (including the host). I usually keep trailing slashes for marketing sites so WordPress habits don't fight me.

5. Sitemap and robots without a plugin

Drop @astrojs/sitemap into the project and set site in astro.config. That covers most of what Yoast's XML sitemap did.

For robots.txt I keep it boring:

User-agent: *
Allow: /

Sitemap: https://client-domain.com/sitemap-index.xml

Only add Disallow rules you can explain out loud. I have seen clients with Yoast blocking half the site by accident. Dont copy that blindly.

6. Structured data: only what you need

I do not port every Yoast schema graph on day one. For a typical local service site I add:

  • Organization (or LocalBusiness) on the homepage
  • WebPage / Article on blog posts if they have a blog
  • BreadcrumbList when the nav is nested

Plain JSON-LD in the layout is fine. Fancy schema plugins can wait until Search Console is clean.

7. Preview check before DNS moves

On the preview deploy I spot-check 10–20 money pages:

  1. View source: title + description match the CSV
  2. Canonical points at the final public URL shape (even if preview host differs, the path should be right)
  3. No accidental noindex left on from staging
  4. Sitemap lists those URLs
  5. Old URLs 301 on the preview host if you already shipped /_redirects

I deploy the preview with npx novence deploy so the client can click real URLs on novence.ai before we touch DNS. Same build that will go live.

Then I crawl the preview with a quick link checker and grep the HTML for empty descriptions. Empty meta descriptions are the silent killer after a migration.

8. After cutover

Once DNS is live:

  • Submit the new sitemap in Search Console
  • Use URL Inspection on the top 5 money pages
  • Watch Coverage / Page indexing for a week
  • Keep the WordPress host up (read-only is fine) until the important URLs show as indexed on the new host

If a page drops, it is almost always a bad redirect or a canonical pointing at a soft 404, not "Astro is bad for SEO".

What I tell the client

You are not losing Yoast. You are moving the same titles and descriptions into the site itself, where they ship with every deploy and dont depend on a PHP plugin staying updated.

That conversation lands better than "we deleted your SEO plugin".

If you are mid-migration and stuck on a weird Yoast template string (%%title%% %%sep%% %%sitename%%), map the tokens once in a small script and write the resolved strings into frontmatter. Do it once. Dont leave template tokens in production HTML.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.