Claude
Skills
Sign in
Back

seo-audit

Included with Lifetime
$97 forever

Comprehensive SEO audit: meta tags, Open Graph/Twitter Card validation, heading hierarchy, image alt coverage, structured data (JSON-LD) extraction, hreflang, internal/external link analysis, and robots.txt/sitemap.xml checking.

Ads & Marketing

What this skill does


# SEO Audit

Perform a comprehensive search engine optimization audit of a web page.
Extracts and validates meta tags (title, description, canonical, robots),
Open Graph and Twitter Card markup, heading hierarchy, image alt text
coverage, structured data (JSON-LD), hreflang annotations, and link
analysis. Supplements with robots.txt and sitemap.xml checks via curl.

## When to Use

- Pre-launch SEO review of new pages or redesigns.
- Diagnosing why a page is not appearing in search results.
- Validating Open Graph and Twitter Card previews before social sharing.
- Auditing structured data (JSON-LD) for rich snippet eligibility.
- Checking heading hierarchy for accessibility and SEO best practices.
- Identifying missing alt text on images.
- Verifying robots.txt and sitemap.xml configuration.

## Prerequisites

- **Playwright MCP server** connected and responding.
- **curl** available in the shell for robots.txt and sitemap.xml fetching.
- Target page must be reachable from the browser instance.

## Workflow

### Phase 1: Navigate to Target

```
browser_navigate({ url: "<target_url>" })
```

```
browser_wait_for({ time: 3 })
```

### Phase 2: Extract Meta Tags and Head Elements

```javascript
browser_evaluate({
  function: `() => {
    const getMeta = (name) => {
      const el = document.querySelector(
        'meta[name="' + name + '"], meta[property="' + name + '"]'
      );
      return el ? el.content : null;
    };

    // Core meta tags
    const meta = {
      title: document.title || null,
      titleLength: (document.title || '').length,
      description: getMeta('description'),
      descriptionLength: (getMeta('description') || '').length,
      robots: getMeta('robots'),
      googlebot: getMeta('googlebot'),
      canonical: null,
      viewport: getMeta('viewport'),
      charset: document.characterSet,
      language: document.documentElement.lang || null
    };

    // Canonical
    const canonicalEl = document.querySelector('link[rel="canonical"]');
    meta.canonical = canonicalEl ? canonicalEl.href : null;

    // Open Graph
    const og = {};
    document.querySelectorAll('meta[property^="og:"]').forEach(el => {
      const prop = el.getAttribute('property').replace('og:', '');
      og[prop] = el.content;
    });

    // Twitter Card
    const twitter = {};
    document.querySelectorAll('meta[name^="twitter:"]').forEach(el => {
      const name = el.getAttribute('name').replace('twitter:', '');
      twitter[name] = el.content;
    });

    // hreflang
    const hreflang = [];
    document.querySelectorAll('link[rel="alternate"][hreflang]').forEach(el => {
      hreflang.push({
        lang: el.hreflang,
        href: el.href
      });
    });

    // Preload/prefetch hints
    const resourceHints = [];
    document.querySelectorAll('link[rel="preload"], link[rel="prefetch"], link[rel="preconnect"], link[rel="dns-prefetch"]').forEach(el => {
      resourceHints.push({
        rel: el.rel,
        href: el.href,
        as: el.getAttribute('as') || null,
        crossorigin: el.crossOrigin || null
      });
    });

    return { meta, og, twitter, hreflang, resourceHints };
  }`
})
```

### Phase 3: Analyze Heading Hierarchy

Use the accessibility tree for accurate heading structure, then supplement
with DOM analysis.

```
browser_snapshot()
```

```javascript
browser_evaluate({
  function: `() => {
    const headings = [];
    document.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((el, index) => {
      headings.push({
        level: parseInt(el.tagName[1]),
        text: el.textContent.trim().substring(0, 100),
        index,
        id: el.id || null,
        isVisible: el.offsetParent !== null,
        parentSection: el.closest('section, article, main, aside, nav')?.tagName || null
      });
    });

    // Check hierarchy issues
    const issues = [];
    const h1Count = headings.filter(h => h.level === 1).length;
    if (h1Count === 0) issues.push('No H1 element found');
    if (h1Count > 1) issues.push('Multiple H1 elements found (' + h1Count + ')');

    for (let i = 1; i < headings.length; i++) {
      const gap = headings[i].level - headings[i - 1].level;
      if (gap > 1) {
        issues.push(
          'Heading level skipped: H' + headings[i - 1].level +
          ' -> H' + headings[i].level +
          ' at "' + headings[i].text.substring(0, 40) + '"'
        );
      }
    }

    return {
      total: headings.length,
      h1Count,
      hierarchy: headings,
      issues
    };
  }`
})
```

### Phase 4: Image Alt Text Coverage

```javascript
browser_evaluate({
  function: `() => {
    const images = [];
    document.querySelectorAll('img').forEach(img => {
      const hasAlt = img.hasAttribute('alt');
      const altText = img.getAttribute('alt');
      const isDecorative = altText === '';
      const isVisible = img.offsetParent !== null && img.naturalWidth > 0;

      images.push({
        src: (img.src || img.dataset.src || '').substring(0, 150),
        alt: altText,
        hasAlt,
        isDecorative,
        isVisible,
        width: img.naturalWidth,
        height: img.naturalHeight,
        loading: img.loading || null,
        fetchpriority: img.fetchPriority || null,
        inViewport: img.getBoundingClientRect().top < window.innerHeight
      });
    });

    const missingAlt = images.filter(i => !i.hasAlt && i.isVisible);
    const emptyAlt = images.filter(i => i.isDecorative && i.isVisible);
    const withAlt = images.filter(i => i.hasAlt && !i.isDecorative);

    return {
      total: images.length,
      visible: images.filter(i => i.isVisible).length,
      missingAlt: missingAlt.length,
      decorative: emptyAlt.length,
      withAlt: withAlt.length,
      coveragePercent: images.length > 0
        ? Math.round((withAlt.length + emptyAlt.length) / images.filter(i => i.isVisible).length * 100)
        : 100,
      images: images.slice(0, 50)
    };
  }`
})
```

### Phase 5: Structured Data (JSON-LD) Extraction

```javascript
browser_evaluate({
  function: `() => {
    const jsonLdScripts = [];
    document.querySelectorAll('script[type="application/ld+json"]').forEach(script => {
      try {
        const data = JSON.parse(script.textContent);
        const items = Array.isArray(data) ? data : [data];
        items.forEach(item => {
          jsonLdScripts.push({
            type: item['@type'] || 'unknown',
            context: item['@context'] || null,
            data: JSON.stringify(item).substring(0, 1000),
            valid: true
          });
        });
      } catch (e) {
        jsonLdScripts.push({
          type: 'parse-error',
          error: e.message,
          raw: script.textContent.substring(0, 200),
          valid: false
        });
      }
    });

    // Also check for microdata
    const microdataItems = [];
    document.querySelectorAll('[itemscope]').forEach(el => {
      microdataItems.push({
        type: el.getAttribute('itemtype') || 'untyped',
        properties: Array.from(el.querySelectorAll('[itemprop]')).map(p => ({
          name: p.getAttribute('itemprop'),
          value: (p.content || p.textContent || p.href || '').substring(0, 100)
        })).slice(0, 20)
      });
    });

    return {
      jsonLd: {
        count: jsonLdScripts.length,
        items: jsonLdScripts
      },
      microdata: {
        count: microdataItems.length,
        items: microdataItems.slice(0, 10)
      }
    };
  }`
})
```

### Phase 6: Link Analysis

```javascript
browser_evaluate({
  function: `() => {
    const pageOrigin = window.location.origin;
    const pageUrl = window.location.href;
    const links = [];
    const issues = [];

    document.querySelectorAll('a[href]').forEach(a => {
      const href = a.href;
      let type = 'internal';
      try {
        const linkOrigin = new URL(href, pageUrl).origin;
        if (linkOrigin !== pageOrigin) type = 'external';
      } catch { type = 'invalid'; }

      if (href.startsWith('javascript:')) type = 'javascri

Related in Ads & Marketing