How to Interpret OpenSEO Site Audit Report Results: A Complete Guide

An OpenSEO site audit report returns a JSON payload with four main sections—audit, pages, lighthouse, and issues—that map directly to database tables and provide a complete picture of your site's technical health.

The every-app/open-seo repository provides a multi-phase auditing workflow that crawls your site, gathers Lighthouse performance data, and identifies broken links and orphan pages. When you call the getAuditResults endpoint, the system aggregates data from multiple database tables into a single coherent document. Understanding how to read each section allows you to prioritize fixes based on actual technical impact rather than raw issue counts.

Understanding the Four Main Sections of an OpenSEO Audit Report

The JSON response from getAuditResults (defined in src/serverFunctions/audit.ts) contains four top-level keys that correspond to physical database tables: audit, pages, lighthouse, and issues.

The Audit Metadata Section

The audit object contains run metadata including id, startUrl, status, pagesCrawled, pagesTotal, and timestamps. It also includes the parsed configuration such as lighthouseStrategy and maxPages.

Why this matters: This section tells you whether the audit status is "completed", "running", or "error". If pagesCrawled is less than pagesTotal, the crawl stopped early due to hitting the maxPages limit or encountering a fatal error. You can cross-reference error statuses with error codes defined in src/server/lib/errors.ts.

The Pages Section

The pages array lists every URL discovered during the crawl with its HTTP statusCode, contentLength, mimeType, and flags like blocked (indicating robots.txt exclusion).

Why this matters: This inventory reveals which URLs were actually reachable. Look for 4xx/5xx responses here to identify server errors or soft 404s that need immediate attention.

The Lighthouse Performance Section

The lighthouse array contains scores and metrics for sampled pages tested on both mobile and desktop. Each entry includes numeric scores for performance, accessibility, bestPractices, and seo, plus an errorMessage field if the fetch failed.

Why this matters: Lighthouse results provide a quantitative health check for page speed and SEO best practices. The count lighthouseTotal should equal twice the number of sampled pages (mobile + desktop). If lighthouseFailed is greater than zero, inspect the corresponding entries for specific errorMessage values.

The Issues Section

The issues array contains detected problems with an issueType (e.g., broken-internal-link, orphan-page), the affected pageId/pageUrl, a dedupeKey, and type-specific details.

Why this matters: This is your actionable todo list. Common issue types include:

  • broken-internal-link — A link on a crawled page returned a non-2xx response.
  • orphan-page — A page discovered in the crawl but not linked from any other page.
  • missing-meta or slow-page — Generated by additional multipage check modules in src/server/lib/audit/issues/multipage.ts.

Each issue's details object contains the exact URL, HTTP status, and remediation context.

How OpenSEO Generates Your Audit Data

Understanding the report structure requires knowing how the data is built. The workflow in src/server/workflows/siteAuditWorkflowPhases.ts orchestrates five distinct phases:

  1. Discovery Phase (runDiscoveryPhase) — Parses robots.txt and extracts sitemaps, storing seeds in a scratch-pad data object.
  2. Crawl Phase (runCrawlPhase) — Fetches URLs and persists them to the AuditRepository.
  3. Lighthouse Phase — Selects sample pages via selectLighthousePages and fetches Lighthouse data via DataForSEO (stored via storeLighthouseResult).
  4. Multipage Checks (runMultipageChecks and runScratchpadLinkChecks) — Runs after crawling to detect broken links and orphan pages.
  5. Finalize (finalizeAudit) — Inserts all issues, sets status to completed, and clears the scratch-pad.

The getAuditResults endpoint simply reads these persisted rows via AuditRepository.getAuditResultsForProject and returns the fully joined view.

Reading Key Status Fields and Metrics

When interpreting the report, focus on these specific fields:

  • status"completed" means all phases in siteAuditWorkflowPhases.ts succeeded; "error" contains an errorCode for debugging.
  • pagesTotal vs. pagesCrawled — A discrepancy indicates the crawl halted early, usually due to the maxPages configuration limit.
  • lighthouseTotal — Should be twice your sampled page count. If lighthouseFailed is non-zero, check the errorMessage in the Lighthouse array entries.
  • dedupeKey — Found in issue objects; allows you to group identical issues across multiple pages without double-counting.

Querying and Displaying Audit Results

You can consume the audit data via React hooks, direct HTTP requests, or server-side service calls.

Fetching Results in a React Component

Use the generated server function with TanStack Query to display audit data:

import { useQuery } from '@tanstack/react-query';
import { getAuditResults } from '@/routes/api/audit';

function AuditReport({ auditId }: { auditId: string }) {
  const { data, isLoading, error } = useQuery(['audit', auditId], () =>
    getAuditResults({ auditId })
  );

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Failed to load audit.</p>;

  const { audit, pages, lighthouse, issues } = data!;

  return (
    <>
      <h2>Audit – {audit.startUrl}</h2>
      <p>Status: {audit.status}</p>

      <section>
        <h3>Crawled Pages ({audit.pagesCrawled}/{audit.pagesTotal})</h3>
        <ul>
          {pages.map(p => (
            <li key={p.id}>
              {p.url} – {p.statusCode}
            </li>
          ))}
        </ul>
      </section>

      <section>
        <h3>Lighthouse Scores</h3>
        <ul>
          {lighthouse.map(l => (
            <li key={l.id}>
              {l.url}: {l.performance} / 100
              {l.errorMessage && <em> – Error: {l.errorMessage}</em>}
            </li>
          ))}
        </ul>
      </section>

      <section>
        <h3>Issues</h3>
        {issues.map(i => (
          <div key={i.pageId} style={{ marginBottom: '0.5rem' }}>
            <strong>{i.issueType}</strong> on {i.pageUrl}
            {i.details && <pre>{JSON.stringify(i.details, null, 2)}</pre>}
          </div>
        ))}
      </section>
    </>
  );
}

Raw API Access via CLI

For automation or external integrations, call the endpoint directly:

curl -X POST https://your-openseo.example.com/api/audit/getAuditResults \
  -H "Content-Type: application/json" \
  -d '{"auditId":"c1d2e3f4-5678-90ab-cdef-1234567890ab"}'

Filter the JSON response with jq to isolate specific issues:


# Show only broken internal links

jq '.issues[] | select(.issueType=="broken-internal-link")' response.json

Server-Side Service Integration

For custom backends or background jobs, use the AuditService directly:

import { AuditService } from '@/server/features/audit/services/AuditService';

async function printSummary(auditId: string, projectId: string) {
  const result = await AuditService.getResults(auditId, projectId);
  console.log(`Audit for ${result.audit.startUrl}`);
  console.log(`Pages crawled: ${result.audit.pagesCrawled}/${result.audit.pagesTotal}`);
  console.log(`Lighthouse failures: ${result.audit.lighthouseFailed}`);
  console.log('Issues:');
  for (const i of result.issues) {
    console.log(`- ${i.issueType} on ${i.pageUrl}`);
  }
}

The AuditService.getResults method in src/server/features/audit/services/AuditService.ts handles the database joins and returns the same structured data used by the API endpoint.

Summary

  • OpenSEO audit reports contain four main sections (audit, pages, lighthouse, issues) that map to database tables in the every-app/open-seo architecture.
  • Always check audit.status to confirm the workflow completed successfully before analyzing other sections.
  • Compare pagesCrawled to pagesTotal to identify if the crawl stopped early due to limits or errors.
  • Review the issues array for actionable items like broken-internal-link and orphan-page, using the details object for remediation specifics.
  • Query results via the React hook, raw HTTP POST, or server-side AuditService depending on your integration needs.

Frequently Asked Questions

What does it mean if pagesCrawled is less than pagesTotal?

This indicates the audit stopped before discovering all potential URLs. According to the workflow logic in src/server/workflows/siteAuditWorkflowPhases.ts, this typically occurs when the crawl hits the maxPages limit defined in your audit configuration or encounters a fatal network error. Check the audit.status field—if it shows "completed", the crawl simply reached its configured cap; if it shows "error", inspect the errorCode for the specific failure reason.

How do I identify which pages failed Lighthouse testing?

Examine the lighthouseFailed count in the audit metadata section, then iterate through the lighthouse array to find entries containing an errorMessage field. The lighthouseTotal should equal twice the number of sampled pages (accounting for both mobile and desktop tests). Failed entries will have null scores and a descriptive error explaining why DataForSEO could not retrieve the metrics.

Can I query the audit data directly from the database instead of using the API?

Yes. The JSON sections returned by getAuditResults correspond directly to the tables audit, audit_pages, audit_lighthouse, and audit_issues. If you need deeper diagnostics or want to run custom SQL analysis, you can query these tables directly. However, the API response in src/serverFunctions/audit.ts provides a fully-joined view that handles the relationships between pages and their associated issues automatically.

A broken-internal-link issue (generated in src/server/lib/audit/issues/multipage.ts) indicates that a crawled page contains a hyperlink returning a 4xx or 5xx status code. An orphan-page issue means the page was discovered (usually via sitemap or direct inclusion) but no other page in the crawl links to it. Broken links represent navigation problems for users, while orphan pages represent architectural isolation that may prevent proper indexing by search engines.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →