Claude AI

Internal Link Audit Claude Skill

This skill audits a website's internal linking structure and produces an actionable SEO report. It crawls pages to find broken internal links, orphan pages, redirect chains, deep pages, weak anchor text, and link equity distribution issues, then recommends concrete fixes.

What The Skill Checks

  • Broken internal links and redirect chains, with the exact source page, target page, and anchor text for each issue.
  • Orphan pages that appear in the sitemap but receive zero inbound internal links, plus pages buried deeper than three clicks from the homepage.
  • Generic anchor text, nofollow on internal links, links to non-canonical or noindexed URLs, and pages with excessive outgoing links.

How The Skill Is Packaged

The skill follows the standard Claude Agent Skill structure: a SKILL.md file with YAML frontmatter and workflow instructions, a references/ folder with the full audit check definitions and report template, and a scripts/ folder with Python scripts that crawl the site and analyze the link graph. Every report can be exported to a shareable Word .docx or a flat .csv of findings via the bundled md_to_docx.py and findings_to_csv.py scripts. Install it with npx claude-seo-skills install internal-link-audit, or copy the skill folder into your Claude skills directory; Claude invokes it automatically when a request matches its description.

Skill Files

Every file in the skill is shown below, copied from the Claude-SEO-Skills repository, so you can review exactly what the skill instructs Claude to do before installing it.

SKILL.md

The skill definition: frontmatter, inputs to collect, the five-step audit workflow, and the resources the skill relies on.

SKILL.mdView on GitHub
name
internal-link-audit
description
Audit a website's internal linking structure for SEO. Crawls pages to find broken internal links, orphan pages, redirect chains, deep pages, missing or weak anchor text, and internal link distribution issues. Use when the user asks to audit, analyze, or improve internal links, site structure, link equity, or crawlability of a website.

Internal Link Audit

Audit the internal linking structure of a website and produce an actionable SEO report.

When to use this skill

Use this skill when the user asks to:

  • Audit or analyze internal links on a website
  • Find broken internal links or redirect chains
  • Identify orphan pages (pages with no internal links pointing to them)
  • Review anchor text quality for internal links
  • Improve crawl depth, site architecture, or internal link equity distribution
Inputs to collect

Before starting, confirm with the user:

  1. Site URL or local files — a live site root URL (e.g., https://example.com), a sitemap URL, or a local folder of HTML files.
  2. Scope — full site, a specific section (e.g., /blog/), or a list of URLs.
  3. Crawl limits — max pages (default 500) and max depth (default 5) for live crawls.
Workflow
Step 1: Gather the page set
  • If a sitemap is available (/sitemap.xml), fetch it to get the canonical page list.
  • Otherwise, crawl from the homepage following only same-host links.
  • For local HTML folders, enumerate all .html files.

Use scripts/crawl_links.py to crawl a live site and extract all internal links into a JSON link graph:

python3 scripts/crawl_links.py https://example.com --max-pages 500 --max-depth 5 --output link_graph.json
Step 2: Build the link graph

For every page, record:

  • Outgoing internal links: target URL, anchor text, rel attributes (nofollow), whether the link is in <nav>/<footer> or in main content
  • HTTP status of each target (200, 3xx, 4xx, 5xx)
  • Page metadata: title, canonical URL, meta robots (noindex, nofollow)
Step 3: Run the audit checks

Use scripts/analyze_graph.py to analyze the link graph:

python3 scripts/analyze_graph.py link_graph.json --output audit_report.json

Evaluate each check defined in references/audit-checks.md. The core checks are:

CheckSeverity
Broken internal links (4xx/5xx targets)High
Orphan pages (in sitemap but zero inbound internal links)High
Redirect chains and redirected internal links (3xx targets)Medium
Deep pages (click depth > 3 from homepage)Medium
Generic anchor text ("click here", "read more", bare URLs)Medium
Pages with excessive outgoing links (> 150)Low
Nofollow on internal linksLow
Links to non-canonical or noindexed URLsMedium
Low inbound link count on important pagesMedium
Step 4: Produce the report

Write a report following the structure in references/report-template.md:

  1. Summary — pages crawled, total internal links, issue counts by severity
  2. Issues — one section per failing check, listing affected source page, target page, anchor text, and a specific fix
  3. Opportunities — pages that deserve more internal links, suggested anchor text using target page keywords
  4. Prioritized action list — ordered by severity and estimated impact
Step 5: Recommend fixes

For each issue, give a concrete, copy-pasteable recommendation, e.g.:

  • Replace the broken link /old-page on /blog/post-1 with /new-page
  • Add 2–3 contextual links to the orphan page /services/seo-audit from topically related pages
  • Change anchor text "click here" on /about to descriptive text containing the target page's primary keyword
Optional: export the report
As a Word document (.docx)

If the user wants the report as a .docx (for example, to share with stakeholders or attach to a ticket), save the Markdown report to a file and convert it:

python3 scripts/md_to_docx.py report.md --output report.docx

scripts/md_to_docx.py uses only the Python standard library (no pip install) and renders headings, tables, lists, links, bold/italic, and code blocks. Offer this whenever a user asks for a Word doc, a .docx, or a shareable/downloadable report.

As a CSV of findings (.csv)

If the user wants the raw findings as a spreadsheet (for filtering, sorting, or triage in Sheets or Excel), convert the audit's audit_report.json directly:

python3 scripts/findings_to_csv.py audit_report.json --output findings.csv

scripts/findings_to_csv.py is also standard-library only. It writes one row per finding, with check and severity columns prepended and list fields (e.g. the pages sharing a duplicate value) joined with ; . Unlike the .docx, which reformats the written report, the CSV is a direct dump of the structured findings — offer it whenever a user wants the data itself, a spreadsheet, or to slice findings by check or severity.

Resources
  • scripts/md_to_docx.py — convert the Markdown report into a Word (.docx) document (standard library only)
  • scripts/findings_to_csv.py — flatten the audit findings JSON into a CSV, one row per finding (standard library only)
  • references/audit-checks.md — full definitions, thresholds, and rationale for every audit check
  • references/report-template.md — report output structure
  • scripts/crawl_links.py — crawl a site and build the internal link graph
  • scripts/analyze_graph.py — run audit checks against a link graph JSON file

Audit Checks Reference (references/audit-checks.md)

Full definitions, thresholds, and rationale for every audit check the skill evaluates.

references/audit-checks.mdView on GitHub

Internal Link Audit Checks

Full definitions, thresholds, and rationale for each audit check.

High severity
1. Broken internal links
  • Definition: An internal <a href> whose target returns HTTP 4xx or 5xx.
  • Why it matters: Wastes crawl budget, loses link equity, and harms user experience.
  • Fix: Update the link to the correct live URL, or remove it. If the target was intentionally removed, 301-redirect it to the closest relevant page.
2. Orphan pages
  • Definition: A page present in the sitemap (or known page set) that receives zero inbound internal links from crawlable pages.
  • Why it matters: Search engines may never discover the page through crawling; it receives no internal link equity.
  • Fix: Add at least 2–3 contextual internal links from topically related pages. Important pages should also be reachable from navigation or hub pages.
Medium severity
3. Redirected internal links and redirect chains
  • Definition: An internal link whose target returns 3xx. A chain is two or more consecutive redirects.
  • Threshold: Flag all 3xx targets; treat chains of 2+ hops as higher priority.
  • Fix: Update links to point directly to the final destination URL.
4. Deep pages (click depth)
  • Definition: Pages requiring more than 3 clicks from the homepage to reach.
  • Threshold: Depth > 3 flagged; depth > 5 is critical.
  • Fix: Link to deep pages from hub/category pages, the homepage, or popular high-depth-0/1 pages.
5. Generic or weak anchor text
  • Definition: Internal links whose anchor text is non-descriptive.
  • Detection list: "click here", "here", "read more", "learn more", "more", "this page", "link", bare URLs, empty anchors (image links without alt text).
  • Fix: Use descriptive anchor text that includes the target page's primary topic or keyword. Avoid exact-match repetition across many links; vary naturally.
6. Links to non-canonical or noindexed URLs
  • Definition: Internal links pointing to a URL whose canonical tag points elsewhere, or whose meta robots is noindex.
  • Why it matters: Sends crawlers and equity to pages that won't be indexed.
  • Fix: Point links at the canonical, indexable URL.
7. Under-linked important pages
  • Definition: High-value pages (conversion pages, key content) with fewer inbound internal links than the site median.
  • Detection: Compare inbound link counts; ask the user which pages are priorities if unclear.
  • Fix: Add contextual links from related, well-linked pages with descriptive anchors.
Low severity
8. Excessive outgoing links
  • Definition: Pages with more than ~150 outgoing internal links.
  • Why it matters: Dilutes equity per link and can indicate poor information architecture.
  • Fix: Trim navigation bloat; split mega-pages into focused hub pages.
9. Nofollow on internal links
  • Definition: Internal links carrying rel="nofollow" (or sponsored/ugc).
  • Why it matters: Internal nofollow blocks equity flow and discovery with no benefit.
  • Fix: Remove nofollow from internal links unless there is a deliberate reason (e.g., faceted URLs you want left uncrawled — prefer robots.txt for that).
Additional signals to note (informational)
  • Self-referencing links (page linking to itself) — usually harmless, note only.
  • Duplicate links (same source → same target multiple times) — only the first anchor typically counts; consolidate where practical.
  • Links using URL parameters or fragments that resolve to the same page — normalize to clean URLs.
  • HTTP links on an HTTPS site — update to HTTPS.

Report Template (references/report-template.md)

The output structure the skill follows when writing the final audit report.

references/report-template.mdView on GitHub

Internal Link Audit Report Template

Use this structure for the final report delivered to the user.

# Internal Link Audit — {site}

**Date:** {date}
**Scope:** {scope description}
**Pages crawled:** {n} | **Internal links analyzed:** {n}

## Summary

| Severity | Issues |
|---|---|
| High | {n} |
| Medium | {n} |
| Low | {n} |

{2–3 sentence overview of the site's internal linking health and the biggest wins.}

## High-severity issues

### Broken internal links ({n})

| Source page | Broken target | Anchor text | Status | Suggested fix |
|---|---|---|---|---|
| ... | ... | ... | 404 | ... |

### Orphan pages ({n})

| Orphan page | Suggested linking pages | Suggested anchor text |
|---|---|---|
| ... | ... | ... |

## Medium-severity issues

{One subsection per failing check, same table style: affected pages, details, specific fix.}

## Low-severity issues

{Brief list or table.}

## Opportunities

{Pages that would benefit from more internal links, with suggested source pages and anchor text.}

## Prioritized action list

1. {Highest-impact fix, with exact pages/links}
2. ...
Guidance
  • Every issue row must name the exact source URL, target URL, and a concrete fix — never generic advice.
  • Cap tables at ~20 rows in the report body; attach or offer the full list (CSV/JSON) if larger.
  • Order the action list by severity first, then by estimated traffic/equity impact.

Link Crawler Script (scripts/crawl_links.py)

Crawls a live site within the configured page and depth limits and extracts all internal links into a JSON link graph.

scripts/crawl_links.pyView on GitHub
#!/usr/bin/env python3
"""Crawl a website and build an internal link graph as JSON.

Usage:
    python3 crawl_links.py https://example.com [--max-pages 500] [--max-depth 5] [--output link_graph.json]

Output JSON schema:
{
  "site": "https://example.com",
  "pages": {
    "<url>": {
      "status": 200,
      "depth": 1,
      "title": "...",
      "canonical": "<url or null>",
      "meta_robots": "<content or null>",
      "links": [
        {"target": "<url>", "anchor": "...", "rel": "nofollow|null", "location": "nav|footer|content"}
      ]
    }
  },
  "statuses": {"<url>": 200}   # status of every linked URL, including non-crawled targets
}

Uses only the Python standard library.
"""

import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
from collections import deque
from html.parser import HTMLParser

USER_AGENT = "InternalLinkAuditBot/1.0 (+claude-skill)"
TIMEOUT = 15


class LinkParser(HTMLParser):
    def __init__(self, base_url):
        super().__init__()
        self.base_url = base_url
        self.links = []
        self.title = None
        self.canonical = None
        self.meta_robots = None
        self._in_title = False
        self._current = None  # link being captured
        self._anchor_parts = []
        self._context = []  # nav/footer nesting

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag in ("nav", "footer"):
            self._context.append(tag)
        elif tag == "title":
            self._in_title = True
        elif tag == "link" and a.get("rel", "").lower() == "canonical" and a.get("href"):
            self.canonical = urllib.parse.urljoin(self.base_url, a["href"])
        elif tag == "meta" and a.get("name", "").lower() == "robots":
            self.meta_robots = a.get("content")
        elif tag == "a" and a.get("href"):
            href = a["href"].strip()
            if href.startswith(("mailto:", "tel:", "javascript:", "#")):
                return
            target = urllib.parse.urljoin(self.base_url, href)
            target = urllib.parse.urldefrag(target)[0]
            location = self._context[-1] if self._context else "content"
            self._current = {
                "target": target,
                "anchor": "",
                "rel": a.get("rel") or None,
                "location": location,
            }
            self._anchor_parts = []
        elif tag == "img" and self._current is not None and a.get("alt"):
            self._anchor_parts.append(a["alt"])

    def handle_endtag(self, tag):
        if tag in ("nav", "footer") and self._context and self._context[-1] == tag:
            self._context.pop()
        elif tag == "title":
            self._in_title = False
        elif tag == "a" and self._current is not None:
            self._current["anchor"] = " ".join(" ".join(self._anchor_parts).split())
            self.links.append(self._current)
            self._current = None

    def handle_data(self, data):
        if self._in_title:
            self.title = (self.title or "") + data.strip()
        if self._current is not None:
            self._anchor_parts.append(data)


def fetch(url):
    """Return (status, final_url, html_or_none)."""
    req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
    try:
        with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
            ctype = resp.headers.get("Content-Type", "")
            body = None
            if "text/html" in ctype:
                body = resp.read(2_000_000).decode("utf-8", errors="replace")
            return resp.status, resp.geturl(), body
    except urllib.error.HTTPError as e:
        return e.code, url, None
    except Exception:
        return 0, url, None  # 0 = network/connection error


def same_host(url, host):
    return urllib.parse.urlparse(url).netloc.lower().lstrip("www.") == host


def main():
    ap = argparse.ArgumentParser(description="Crawl a site and build an internal link graph.")
    ap.add_argument("start_url")
    ap.add_argument("--max-pages", type=int, default=500)
    ap.add_argument("--max-depth", type=int, default=5)
    ap.add_argument("--output", default="link_graph.json")
    args = ap.parse_args()

    start = args.start_url.rstrip("/")
    host = urllib.parse.urlparse(start).netloc.lower().lstrip("www.")
    pages, statuses = {}, {}
    queue = deque([(start, 0)])
    seen = {start}

    while queue and len(pages) < args.max_pages:
        url, depth = queue.popleft()
        status, final_url, html = fetch(url)
        statuses[url] = status
        print(f"[{status}] depth={depth} {url}", file=sys.stderr)

        page = {"status": status, "depth": depth, "title": None,
                "canonical": None, "meta_robots": None, "links": []}
        if html:
            parser = LinkParser(final_url)
            try:
                parser.feed(html)
            except Exception:
                pass
            page.update(title=parser.title, canonical=parser.canonical,
                        meta_robots=parser.meta_robots)
            for link in parser.links:
                if not same_host(link["target"], host):
                    continue
                page["links"].append(link)
                t = link["target"]
                if t not in seen and depth + 1 <= args.max_depth:
                    seen.add(t)
                    queue.append((t, depth + 1))
        pages[url] = page

    # Resolve statuses for linked-but-uncrawled targets
    linked = {l["target"] for p in pages.values() for l in p["links"]}
    for t in sorted(linked - set(statuses)):
        status, _, _ = fetch(t)
        statuses[t] = status
        print(f"[{status}] (status-check) {t}", file=sys.stderr)

    with open(args.output, "w") as f:
        json.dump({"site": start, "pages": pages, "statuses": statuses}, f, indent=2)
    print(f"Wrote {len(pages)} pages, {len(statuses)} URL statuses to {args.output}", file=sys.stderr)


if __name__ == "__main__":
    main()

Graph Analyzer Script (scripts/analyze_graph.py)

Runs the audit checks against the link graph JSON and outputs the structured audit report data.

scripts/analyze_graph.pyView on GitHub
#!/usr/bin/env python3
"""Analyze an internal link graph JSON (from crawl_links.py) and emit audit findings.

Usage:
    python3 analyze_graph.py link_graph.json [--output audit_report.json]

Checks implemented (see references/audit-checks.md):
- broken_links: internal links to 4xx/5xx targets (high)
- orphan_pages: crawled pages with zero inbound internal links (high)
- redirected_links: internal links to 3xx targets (medium)
- deep_pages: pages with click depth > 3 (medium)
- generic_anchors: weak anchor text (medium)
- noncanonical_targets: links to pages whose canonical differs or are noindexed (medium)
- excessive_outlinks: pages with > 150 outgoing internal links (low)
- nofollow_internal: internal links with rel nofollow/sponsored/ugc (low)

Uses only the Python standard library.
"""

import argparse
import json
import sys
from collections import Counter

GENERIC_ANCHORS = {
    "click here", "here", "read more", "learn more", "more",
    "this page", "link", "this", "continue", "view", "see more", "",
}
MAX_DEPTH = 3
MAX_OUTLINKS = 150


def main():
    ap = argparse.ArgumentParser(description="Run internal link audit checks on a link graph.")
    ap.add_argument("graph", help="link_graph.json from crawl_links.py")
    ap.add_argument("--output", default="audit_report.json")
    args = ap.parse_args()

    with open(args.graph) as f:
        data = json.load(f)
    pages = data["pages"]
    statuses = data.get("statuses", {})

    inbound = Counter()
    for src, page in pages.items():
        for link in page["links"]:
            if link["target"] != src:
                inbound[link["target"]] += 1

    findings = {
        "broken_links": [], "orphan_pages": [], "redirected_links": [],
        "deep_pages": [], "generic_anchors": [], "noncanonical_targets": [],
        "excessive_outlinks": [], "nofollow_internal": [],
    }

    home_depth0 = min((p["depth"] for p in pages.values()), default=0)

    for src, page in pages.items():
        if len(page["links"]) > MAX_OUTLINKS:
            findings["excessive_outlinks"].append(
                {"page": src, "outgoing_links": len(page["links"])})
        if page["depth"] - home_depth0 > MAX_DEPTH:
            findings["deep_pages"].append({"page": src, "depth": page["depth"]})

        for link in page["links"]:
            target, anchor = link["target"], link["anchor"]
            status = statuses.get(target)
            entry = {"source": src, "target": target, "anchor": anchor}

            if status is not None:
                if status >= 400 or status == 0:
                    findings["broken_links"].append({**entry, "status": status})
                elif 300 <= status < 400:
                    findings["redirected_links"].append({**entry, "status": status})

            if anchor.lower().strip() in GENERIC_ANCHORS:
                findings["generic_anchors"].append(entry)

            rel = (link.get("rel") or "").lower()
            if any(r in rel for r in ("nofollow", "sponsored", "ugc")):
                findings["nofollow_internal"].append({**entry, "rel": link["rel"]})

            tpage = pages.get(target)
            if tpage:
                robots = (tpage.get("meta_robots") or "").lower()
                canonical = tpage.get("canonical")
                if "noindex" in robots:
                    findings["noncanonical_targets"].append({**entry, "reason": "noindex"})
                elif canonical and canonical.rstrip("/") != target.rstrip("/"):
                    findings["noncanonical_targets"].append(
                        {**entry, "reason": "canonical points elsewhere",
                         "canonical": canonical})

    for url, page in pages.items():
        if inbound[url] == 0 and page["depth"] > home_depth0:
            findings["orphan_pages"].append({"page": url})

    severity = {
        "broken_links": "high", "orphan_pages": "high",
        "redirected_links": "medium", "deep_pages": "medium",
        "generic_anchors": "medium", "noncanonical_targets": "medium",
        "excessive_outlinks": "low", "nofollow_internal": "low",
    }
    summary = {
        "pages_analyzed": len(pages),
        "total_internal_links": sum(len(p["links"]) for p in pages.values()),
        "issues_by_check": {k: len(v) for k, v in findings.items()},
        "issues_by_severity": {
            level: sum(len(findings[k]) for k, s in severity.items() if s == level)
            for level in ("high", "medium", "low")
        },
    }
    report = {"summary": summary, "severity": severity, "findings": findings,
              "inbound_link_counts": dict(inbound)}

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)
    print(json.dumps(summary, indent=2))
    print(f"Full report written to {args.output}", file=sys.stderr)


if __name__ == "__main__":
    main()

Explore Other Claude SEO Skills

External Link Audit Audits a site's external links for broken targets, redirects, insecure HTTP links, missing rel attributes on affiliate links, and unsafe target="_blank" usage. external-link-audit Open Meta Data Audit Audits a site's title tags and meta descriptions for missing, duplicate, too-long, too-short, or multiple tags, generic boilerplate titles, and keyword stuffing. meta-data-audit Open Schema Markup Audit Audits a site's structured data: extracting JSON-LD, Microdata, and RDFa, then validating it against schema.org and Google rich result requirements. schema-markup-audit Open Site Architecture Audit Audits a site's architecture: hostname and protocol canonicalization, robots.txt, XML sitemap parity, crawl and index directive conflicts, URL structure, click depth, and directory taxonomy. site-architecture-audit Open Heading Structure Audit Audits a site's heading hierarchy for missing or multiple H1s, skipped heading levels, empty headings, headings that don't start at H1, overly long headings, and duplicate heading text. heading-structure-audit Open Image SEO Audit Audits a site's images for SEO and Core Web Vitals: missing alt text, absent width/height, missing lazy-loading, oversized files, legacy formats, generic filenames, and broken images. image-seo-audit Open Core Web Vitals Audit Audits a site's Core Web Vitals and page-experience signals (LCP, CLS, INP, TTFB), pulling real CrUX field data from PageSpeed Insights and collecting lab proxies like render-blocking resources, page weight, and image hygiene. core-web-vitals-audit Open Redirect Audit Audits a site's redirects for redirect chains and loops, redirects ending in 404s or errors, 302s used for permanent moves, HTTPS-to-HTTP downgrades, client-side redirects, dropped query strings, and canonicals pointing at redirecting URLs. redirect-audit Open Robots.txt Audit Audits a site's robots.txt for a redirected or HTML-served file, a 5xx read as block-everything, a sitewide Disallow, syntax errors, blocked CSS/JS, missing Sitemap directives, unsupported directives, oversized files, and encoding issues. robots-txt-audit Open Sitemap Audit Audits a site's XML sitemaps: discovering them from robots.txt, following sitemap index files, and validating every URL for broken or redirecting targets, noindexed or canonicalized pages, wrong host or protocol, duplicates, oversized sitemaps, and invalid lastmod values. sitemap-audit Open Llms.txt Audit Audits a site's /llms.txt for AI and LLM discoverability: missing files, HTML served instead of markdown, a missing H1 or summary, broken or redirecting links, links missing descriptions, wrong-host or relative URLs, and a companion /llms-full.txt. llms-txt-audit Open Canonical Tag Audit Audits a site's rel=canonical tags for missing canonicals, multiple conflicting canonicals, canonicals pointing at non-200, redirecting, or noindexed URLs, relative or cross-host canonicals, https-to-http downgrades, and Link-header conflicts. canonical-tag-audit Open Content Quality Audit Audits a site's content quality for thin content, exact-duplicate body text across pages, near-duplicate (templated) pages, low text-to-HTML ratio, and pages missing an H1. content-quality-audit Open Keyword Cannibalization Audit Audits a site for keyword cannibalization: pages with duplicate title targets, clusters of pages whose title, H1, and keyword signatures overlap, and pages sharing the same primary keyword phrase, with a recommended hierarchy. keyword-cannibalization-audit Open Mixed Content Audit Audits an HTTPS site for mixed content: active mixed content (http scripts, styles, iframes browsers block), insecure form actions, passive mixed content (http images and media), protocol-relative URLs, and pages still served over http. mixed-content-audit Open Open Graph Audit Audits a site's Open Graph and Twitter Card metadata: missing og:title, og:description, og:image, or og:url, broken or relative og:image URLs, og:url that mismatches the canonical, missing twitter:card, over-length social titles, and reused default images. open-graph-audit Open Pagination Audit Audits a site's pagination: paginated pages canonicalizing to page 1 and hiding deep content, noindexed component pages, paginated pages missing a self-canonical, broken or inconsistent rel=next/prev, and redundant ?page=1 first pages. pagination-audit Open Soft 404 Audit Audits a site for soft 404s: error pages served with a 200 status, servers that return 200 for every unknown URL, thin pages matching the site's error template, and empty or near-empty pages. soft-404-audit Open Full SEO Audit The orchestrator skill that runs every individual audit below and consolidates the findings into one prioritized, cross-area report ranked by impact, for a complete technical-SEO review in a single pass. full-seo-audit Open