/** * SEO Audit Report Generator * Generates standalone HTML reports from audit data */ export interface SeoAuditData { url: string; domain: string; responseTime: number; server: string; htmlSize: number; title: string | null; titleLength: number; titleStatus: string; metaDescription: string | null; descLength: number; descStatus: string; metaKeywords: string | null; viewport: string | null; charset: string | null; robotsDirectives: string | null; canonical: string | null; hasCanonicalMismatch: boolean; h1Count: number; h2Count: number; h3Count: number; h4Count: number; headingStatus: string; headings: { level: number; text: string }[]; links?: { total: number; internal: number; external: number; nofollow: number }; images?: { total: number; withAlt: number; withoutAlt: number; lazyLoaded: number; altCoverage: number }; content?: { wordCount: number; sentenceCount: number; paragraphCount: number; avgWordsPerSentence: number }; openGraph?: { title: string | null; description: string | null; image: string | null; type: string | null }; twitterCard?: { card: string | null }; performance?: { inlineStyles: number; externalScripts: number; externalStylesheets: number; hasPreconnect: boolean; hasPreload: boolean; hasDnsPrefetch: boolean; usesAsyncScripts: boolean; usesDeferScripts: boolean; }; accessibility?: { hasLangAttr: boolean; hasAriaLabels: boolean; hasAltOnFirstImage: boolean }; structuredData?: { hasJsonLd: boolean; hasMicrodata: boolean; types: { type: string; found: boolean }[] }; scores?: { overall: number; technical: number; content: number; performance: number; social: number }; issues?: { severity: string; category: string; message: string }[]; } const scoreColor = (s: number): string => s >= 80 ? "#22c55e" : s >= 60 ? "#f59e0b" : "#ef4444"; const sevColor = (s: string): string => s === "critical" ? "#ef4444" : s === "warning" ? "#f59e0b" : "#6b7280"; const fixInstructions: Record = { Meta: "Ensure every page has a unique title tag (50-60 chars) with primary keyword near start. Write meta descriptions (150-160 chars) with CTA. Add viewport meta tag.", Content: "Maintain exactly one H1 per page with primary keyword. Build logical heading hierarchy (H1>H2>H3). Aim for 1000+ words on core pages.", Technical: "Set self-referencing canonical tags. Ensure valid SSL with HTTPS redirect. Check robots.txt is not blocking important pages.", Mobile: "Add viewport meta tag. Test with Google Mobile-Friendly tool. Ensure tap targets are 48x48px minimum.", Security: "Migrate to HTTPS with valid SSL. Set up HTTP-to-HTTPS redirects. Configure HSTS headers.", Performance: "Minimize inline styles. Add preconnect hints. Implement lazy loading. Use async/defer for scripts. Compress images.", Social: "Add OG tags (og:title, og:description, og:image). Add Twitter Card meta. Ensure OG images are 1200x630px+.", Accessibility: "Add lang attribute to . Implement ARIA labels. Add descriptive alt text to all images.", Links: "Fix broken internal links. Add external links to authoritative sources. Review nofollow attributes.", "Structured Data": "Implement JSON-LD schema. Validate with Google Rich Results Test. Add FAQ/Article/Product schema.", }; export function generateSeoReportHtml(d: SeoAuditData): string { const now = new Date().toLocaleString(); const sc = scoreColor; const svc = sevColor; const issueRows = (d.issues || []) .map( (issue) => "" + issue.severity.toUpperCase() + "" + issue.category + "" + issue.message + "" + (fixInstructions[issue.category] || "Review and address this issue.") + "" ) .join(""); const headingRows = (d.headings || []) .map((h) => "H" + h.level + "" + h.text + "") .join(""); const t = (v: string | null | undefined, fallback = "None") => v || "" + fallback + ""; return "SEO Audit - " + (d.domain || "report") + "

SEO/GEO Audit Report

" + (d.url || "N/A") + "

PromptArch Vibe Architect | " + now + "

" + (d.scores?.overall || 0) + "
Overall
" + (d.scores?.technical || 0) + "
Technical
" + (d.scores?.content || 0) + "
Content
" + (d.scores?.performance || 0) + "
Performance
" + (d.scores?.social || 0) + "
Social
" + // Meta Tags "

Meta Tags

ElementValueStatus
Title" + t(d.title, "MISSING") + " (" + (d.titleLength || 0) + " chars)" + (d.titleStatus || "?") + "
Description" + t(d.metaDescription, "MISSING") + " (" + (d.descLength || 0) + " chars)" + (d.descStatus || "?") + "
Viewport" + t(d.viewport, "MISSING") + "" + (d.viewport ? "OK" : "Missing") + "
Canonical" + (d.canonical || "None") + "" + (d.hasCanonicalMismatch ? "MISMATCH" : d.canonical ? "OK" : "Missing") + "
Robots" + (d.robotsDirectives || "None") + "
" + // Social "

Social / Open Graph

PropertyValue
OG Title" + (d.openGraph?.title || "None") + "
OG Description" + (d.openGraph?.description || "None") + "
OG Image" + (d.openGraph?.image || "None") + "
OG Type" + (d.openGraph?.type || "None") + "
Twitter Card" + (d.twitterCard?.card || "None") + "
" + // Headings "

Headings

H1: " + d.h1Count + " | H2: " + d.h2Count + " | H3: " + d.h3Count + " | H4: " + d.h4Count + " [" + (d.headingStatus || "?") + "]

" + (headingRows ? "" + headingRows + "
LevelText
" : "") + "
" + // Links & Images "

Links & Images

MetricValue
Total Links" + (d.links?.total || 0) + "
Internal" + (d.links?.internal || 0) + "
External" + (d.links?.external || 0) + "
Nofollow" + (d.links?.nofollow || 0) + "
Images" + (d.images?.total || 0) + " (" + (d.images?.altCoverage || 0) + "% alt)
Missing Alt" + (d.images?.withoutAlt || 0) + "
Lazy Loaded" + (d.images?.lazyLoaded || 0) + "
" + // Content "

Content

MetricValue
Words" + (d.content?.wordCount || 0) + "
Sentences" + (d.content?.sentenceCount || 0) + "
Avg Words/Sentence" + (d.content?.avgWordsPerSentence || 0) + "
" + // Performance "

Performance

MetricValue
Server" + (d.server || "Unknown") + "
Response Time" + d.responseTime + "ms
HTML Size" + (d.htmlSize || 0).toLocaleString() + " bytes
External Scripts" + (d.performance?.externalScripts || 0) + "
Inline Styles" + (d.performance?.inlineStyles || 0) + "
Preconnect" + (d.performance?.hasPreconnect ? "Yes" : "No") + "
Async/Defer" + (d.performance?.usesAsyncScripts || d.performance?.usesDeferScripts ? "Yes" : "No") + "
" + // Issues "

Issues & How to Fix

" + (d.issues?.length || 0) + " issues found

" + (issueRows || "") + "
SeverityCategoryIssueHow to Fix
No issues.
" + // Recommendations "

SEO/GEO Recommendations

  1. Title Tags: Keep under 60 chars, primary keyword near start, unique per page.
  2. Meta Descriptions: 150-160 chars with CTA, include target keywords naturally.
  3. Headings: One H1 per page, logical hierarchy, keywords in headings.
  4. Content: 1000+ words on core pages, authoritative, regularly updated.
  5. Images: Descriptive alt text, WebP format, lazy loading, compressed.
  6. Page Speed: Minimize render-blocking resources, enable compression, use CDN.
  7. Mobile: Responsive design, 48x48px tap targets, no horizontal scroll.
  8. Structured Data: JSON-LD schema, validate with Google Rich Results Test.
  9. Internal Linking: Logical architecture, descriptive anchors, 3-click rule.
  10. Technical: XML sitemap, robots.txt, fix broken links, canonical tags.
  11. GEO: Structure content with clear facts/lists/tables. Use schema for entities. Optimize for featured snippets. Create authoritative content AI models can cite.

PromptArch Vibe Architect | " + now + "

rommark.dev/tools/promptarch

"; } export function downloadSeoReport(d: SeoAuditData, format: "html" | "pdf") { const html = generateSeoReportHtml(d); if (format === "html") { const blob = new Blob([html], { type: "text/html" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "seo-audit-" + (d.domain || "report") + "-" + new Date().toISOString().slice(0, 10) + ".html"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } else { const win = window.open("", "_blank"); if (win) { win.document.write(html); win.document.close(); setTimeout(() => win.print(), 500); } } }