← All use cases
Content & MarketingXPathRegex

SEO Title and Meta Description Drift Detection

Catch silent SEO-critical metadata changes on your own pages or a competitor's - the kind of drift that explains last week's traffic dip.

Verid Use Cases·4 min read

The scenario

A page on your own site used to rank well for a specific keyword. Last week it dropped two positions. Looking at the page, the title tag now reads slightly differently than what you optimized for - someone edited it during an unrelated content update and didn't realize the title was part of the SEO surface. Or the CMS auto-generated a fresh meta description that's two hundred characters of mush.

For competitive analysts, the same monitor on a competitor's page catches the moment they re-target a keyword.

The problem

Title and meta-description drift is silent. Nothing visibly changes on the page. A site audit catches it monthly, by which point you've already lost two weeks of ranking. No alerting tool focused on SEO routinely watches these fields per-page.

How Verid solves it

Title and meta description are reliable HTML elements with stable selectors. XPath is the cleanest tool for selecting them (since both live inside <head>); regex against the raw HTML is the equally-reliable alternative when you want a single field.

Verid extracts both as fields and fires on any change. Pair it with a list of your top-trafficked URLs and you'll know within minutes when one drifts.

Build the monitor

Extraction config - XPath

{
  "method": "xpath",
  "fields": {
    "title": "//title/text()",
    "meta_description": "//meta[@name='description']/@content",
    "canonical": "//link[@rel='canonical']/@href",
    "og_title": "//meta[@property='og:title']/@content"
  }
}

Extraction config - Regex

{
  "method": "regex",
  "fields": {
    "title": "<title[^>]*>([^<]+)</title>",
    "meta_description": "<meta\\s+name=\"description\"\\s+content=\"([^\"]+)\""
  }
}

Predicate

{ "type": "any_field_changes" }

Or, to fire only on the title (the most ranking-sensitive field):

{ "type": "field_changes", "field": "title" }

Create the monitor

curl -X POST https://api.verid.dev/v1/monitors \
  -H "Authorization: Bearer vrd_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SEO - /pricing page",
    "url": "https://example.com/pricing",
    "schedule_interval_seconds": 21600,
    "extract_config": {
      "method": "xpath",
      "fields": {
        "title": "//title/text()",
        "meta_description": "//meta[@name=\"description\"]/@content",
        "canonical": "//link[@rel=\"canonical\"]/@href",
        "og_title": "//meta[@property=\"og:title\"]/@content"
      }
    },
    "diff_predicate": { "type": "any_field_changes" },
    "deliveries": [
      { "type": "slack", "webhookUrl": "https://hooks.slack.com/services/..." }
    ]
  }'

SDK:

import { VeridClient } from '@verid.dev/sdk';

const client = new VeridClient({ apiKey: 'vrd_your_api_key' });

await client.monitors.create({
  name: 'SEO - /pricing page',
  url: 'https://example.com/pricing',
  schedule_interval_seconds: 21600,
  extract_config: {
    method: 'xpath',
    fields: {
      title: '//title/text()',
      meta_description: "//meta[@name='description']/@content",
      canonical: "//link[@rel='canonical']/@href",
      og_title: "//meta[@property='og:title']/@content",
    },
  },
  diff_predicate: { type: 'any_field_changes' },
  deliveries: [
    { type: 'slack', webhookUrl: 'https://hooks.slack.com/services/...' },
  ],
});

What the webhook delivers

{
  "id": "del_01H...",
  "fired_at": "2026-05-08T08:00:00Z",
  "diff": {
    "fields_changed": ["title", "meta_description"],
    "before": {
      "title": "Pricing - Example Product",
      "meta_description": "Plans starting at $20/mo. Free tier with 5 monitors.",
      "canonical": "https://example.com/pricing",
      "og_title": "Pricing - Example Product"
    },
    "after": {
      "title": "Plans & Pricing | Example",
      "meta_description": "Choose the plan that fits your team. See features and FAQ.",
      "canonical": "https://example.com/pricing",
      "og_title": "Plans & Pricing | Example"
    }
  }
}

The title was rewritten and lost the "Pricing" keyword as the primary token; the meta description swapped a concrete CTA for vague copy. Both worth a conversation with whoever pushed the change.

Caveats & tips

  • Cover the URLs that actually matter. Don't try to monitor every URL on a large site - pick the top 20-50 by traffic or revenue contribution and watch those. That's where drift costs money.
  • Server-rendered vs client-rendered titles. A React app may have a placeholder <title> in the HTML that gets replaced by client JS after load. If your XPath extractor returns the placeholder, set fetch_mode: 'browser' so the page renders before extraction.
  • Canonical drift is its own alert. A change in the canonical URL means Google is being told a different page is the authoritative version - usually a bug worth catching the same day.
  • Bulk this with one monitor per page. Verid's tier limits are per-monitor; one per URL keeps diffs clean and alerts targeted.

Related use cases

For full-content competitor copy changes, see competitor ad & landing copy changes. For sitemap-level discovery, see sitemap new URLs. For terms-of-service drift, see terms of service & privacy policy drift.

Ship this monitor today

5 monitors free, no credit card. Set up takes about a minute.

Get started free