# How Audit Issue Reporters Identify Common SEO Problems in Open SEO

> Discover how Open SEO audit reporters pinpoint common SEO issues. They analyze page signals and run cross-page algorithms for duplicate content and redirect loops.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Audit issue reporters in Open SEO identify common SEO problems through a layered pipeline that checks per-page signals like missing titles and slow responses, then runs cross-page algorithms to detect duplicates and redirect loops.**

Open SEO uses a systematic audit pipeline to transform raw crawl data into actionable SEO insights. The **audit issue reporters** work by comparing crawled page attributes against a centralized catalogue of SEO rules, enabling automated detection of technical problems ranging from missing meta tags to site-wide duplicate content.

## The Centralized Issue Catalogue

All possible SEO problems are defined once in the shared module **[`src/shared/audit-issues.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/audit-issues.ts)**. This central registry ensures consistent wording and severity levels across both the server-side engine and the client UI.

Each entry in the `AUDIT_ISSUE_TYPES` constant contains a severity level, human-readable title, detailed explanation, and concrete remediation steps:

```ts
export const AUDIT_ISSUE_TYPES = {
  "missing-title": { severity: "critical", title: "Missing title tag", … },
  "duplicate-meta-description": { … },
  // … (≈ 30 issue types)
} as const;

```

The catalogue contains approximately 30 distinct issue types, providing a single source of truth for the entire audit system.

## Per-Page SEO Detection with `runPageReporters`

For every crawled page, the pure function `runPageReporters` in **[`src/server/lib/audit/issues/page-reporters.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/page-reporters.ts)** executes a series of DOM-free checks. The function accepts a `CrawledPageResult` object and returns an array of `DetectedIssue` objects referencing the central catalogue.

The reporter evaluates the following SEO signal categories:

- **Fetch status** – Detects `blocked-page` when bot protection triggers, `server-error` for HTTP 5xx responses, and `broken-page` for HTTP 4xx errors (lines 49‑64).
- **Performance** – Flags `slow-response` when response time exceeds 1.5 seconds (lines 70‑72).
- **Title tags** – Identifies `missing-title`, `title-too-long`, and `title-too-short` based on length thresholds (lines 80‑87).
- **Meta descriptions** – Reports `missing-meta-description`, `meta-description-too-long`, and `meta-description-too-short` (lines 90‑99).
- **Heading structure** – Catches `missing-h1`, `multiple-h1`, and `heading-order-skip` when hierarchy is violated (lines 101‑110).
- **Canonical and indexability** – Detects `noindex-page`, `canonical-conflict`, and `canonicalized-page` issues (lines 112‑133).
- **Content quality** – Flags `thin-content` for pages under 150 words and `images-missing-alt` for accessibility gaps (lines 134‑144).
- **Link architecture** – Identifies `no-outgoing-links` and `deep-page` for URLs buried deeper than five clicks (lines 46‑52).

Each condition pushes a `DetectedIssue` into the result array using a simple pattern:

```ts
if (!page.title) report("missing-title");

```

### Practical Example: Running Page-Level Checks

You can invoke the per-page reporter directly for unit testing or custom integrations:

```ts
import { runPageReporters } from "@/server/lib/audit/issues/page-reporters";
import type { CrawledPageResult } from "@/server/lib/audit/types";

const page: CrawledPageResult = {
  id: "p1",
  url: "https://example.com/about",
  fetchClass: "ok",
  statusCode: 200,
  responseTimeMs: 2100,
  isHtml: true,
  title: "",
  metaDescription: "Short",
  h1Count: 0,
  headingOrder: [1, 3],
  isIndexable: true,
  canonicalUrl: null,
  headerCanonicalUrl: null,
  wordCount: 90,
  imagesMissingAlt: 2,
  imagesTotal: 10,
  links: [],
  crawlDepth: 6,
};

const issues = runPageReporters(page);
console.log(issues.map(i => i.issueType));
/* → [
   "slow-response",
   "missing-title",
   "title-too-short",
   "missing-h1",
   "heading-order-skip",
   "thin-content",
   "images-missing-alt",
   "no-outgoing-links",
   "deep-page"
] */

```

This implementation in [`src/server/lib/audit/issues/page-reporters.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/page-reporters.ts) processes each page independently, ensuring fast, stateless evaluation.

## Cross-Page (Multipage) Analysis

Some SEO problems require visibility into the entire site structure. The **[`src/server/lib/audit/issues/multipage-checks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/multipage-checks.ts)** module operates on a lightweight `SlimPage` representation to detect site-wide patterns without loading full page content into memory.

### Duplicate Content Detection

The `findDuplicates` function groups pages by title, meta description, and content hash. Any group with two or more members generates `duplicate-title`, `duplicate-meta-description`, or `duplicate-content` issues for all affected URLs.

```ts
import { findDuplicates } from "@/server/lib/audit/issues/multipage-checks";

const pages = [
  { id: "1", url: "/a", statusCode: 200, fetchClass: "ok", title: "Home", metaDescription: "Home page", contentHash: "hash1", wordCount: 300, isIndexable: true, canonicalUrl: null, headerCanonicalUrl: null },
  { id: "2", url: "/b", statusCode: 200, fetchClass: "ok", title: "Home", metaDescription: "About us", contentHash: "hash2", wordCount: 250, isIndexable: true, canonicalUrl: null, headerCanonicalUrl: null },
];
const dupIssues = findDuplicates(pages);
console.log(dupIssues);
/* → [{ issueType: "duplicate-title", pageId: "1", … }, { issueType: "duplicate-title", pageId: "2", … }] */

```

### Redirect Chain and Loop Detection

The system walks the graph of 3xx redirects to identify structural problems. Long chains emit `redirect-chain` issues, while cycles trigger `redirect-loop` alerts. These checks run after the per-page reporters complete, ensuring all crawl data is available for graph analysis.

## Orchestration Through the MCP API

The audit pipeline is exposed through Model Context Protocol (MCP) tools defined in **[`src/server/mcp/tools/site-audit-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/site-audit-tools.ts)**. The `run_site_audit` tool initiates the process:

1. Crawls the target site and stores each `CrawledPageResult`.
2. Calls `runPageReporters` for every crawled page to detect single-page issues.
3. Persists pages to the database and executes multipage checks (`findDuplicates`, `findRedirectChainsAndLoops`).
4. Stores results in the `audit_issues` table.

The `get_audit_issues` endpoint retrieves the final list, sorting by severity and decorating each row with human-friendly descriptions from `AUDIT_ISSUE_TYPES`.

### Starting an Audit via API

```ts
// Example: start an audit for https://example.com
await fetch("https://api.openseo.com/mcp/run_site_audit", {
  method: "POST",
  headers: { "Content-Type": "application/json", Authorization: "Bearer <token>" },
  body: JSON.stringify({
    projectId: "proj_123",
    url: "https://example.com",
    maxPages: 200,
    runLighthouse: true,
  }),
});
// The response contains an auditId; poll get_audit_status until completed.

```

The workflow driver in **[`src/server/workflows/siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/siteAuditWorkflowPhases.ts)** manages execution phases, ensuring reporters run only after crawl completion.

## Summary

- **Audit issue reporters** rely on a centralized catalogue in [`src/shared/audit-issues.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/audit-issues.ts) to maintain consistent issue definitions across the platform.
- **Per-page detection** occurs through the pure function `runPageReporters`, which evaluates fetch status, performance, titles, meta descriptions, headings, canonicals, content quality, and link structure without DOM parsing.
- **Cross-page analysis** uses `findDuplicates` and `findRedirectChainsAndLoops` to detect site-wide problems like duplicate content and redirect loops.
- **MCP orchestration** via [`src/server/mcp/tools/site-audit-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/site-audit-tools.ts) coordinates crawling, analysis, and result persistence through standardized API endpoints.

## Frequently Asked Questions

### How does Open SEO determine the severity of an SEO issue?

Severity levels are hardcoded in the `AUDIT_ISSUE_TYPES` registry within [`src/shared/audit-issues.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/audit-issues.ts). Each issue type specifies a severity of "critical", "warning", or "info" based on the potential impact on search rankings. For example, `missing-title` carries critical severity because it directly impacts click-through rates from search results, while `title-too-long` might warrant a warning.

### Can I run the audit issue reporters on a single page without crawling an entire site?

Yes. The `runPageReporters` function in [`src/server/lib/audit/issues/page-reporters.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/page-reporters.ts) is a pure function that accepts a single `CrawledPageResult` object. You can construct this object manually or from any data source and call the function directly without invoking the full MCP crawl workflow, making it suitable for unit tests or custom integrations.

### What is the difference between page-level and multipage SEO checks?

Page-level checks evaluate attributes of individual URLs in isolation, such as title length or response time, using `runPageReporters`. Multipage checks require analyzing relationships between multiple URLs, such as detecting duplicate titles across different pages or identifying redirect chains that span several URLs. These are handled by `findDuplicates` and `findRedirectChainsAndLoops` in [`src/server/lib/audit/issues/multipage-checks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/audit/issues/multipage-checks.ts).

### How are detected audit issues stored and retrieved?

After detection, issues are persisted to the `audit_issues` database table. The MCP endpoint `get_audit_issues` queries this table, sorts results by severity, and enriches each record with human-readable titles and remediation steps from the `AUDIT_ISSUE_TYPES` catalogue. This architecture separates detection logic from storage, allowing reporters to remain stateless while the database maintains the audit history.