Santaji GadeJavaScript3 days ago13 Views

Build an SEO audit tool using JavaScript with five real DOM-based checks and a weighted scoring system — turn it into a one-click bookmarklet.
Table of Contents
ToggleBuilding an SEO audit tool using JavaScript means writing rule-based checks against the DOM, title length, meta description, heading hierarchy, missing alt text, canonical presence, then scoring the results. It's genuinely simpler than it sounds. A handful of focused check functions and a scoring pass cover the checks that matter most for a real page.
One reference audit platform bundles well over 100 checks across technical, on-page, and structured-data categories. A useful DIY tool doesn't need all of them. Focus on the checks with the biggest, most reliably measurable impact: title and meta length, heading hierarchy, image alt coverage, and canonical presence.
Each check follows the same shape: query the relevant DOM elements, evaluate them against a known-good threshold, and return a structured result with a pass/fail status and a human-readable message.
SiteGuru's on-page checker treats these two as foundational: does the page have a title and description, and are they within the length range search engines display without truncating.
function checkTitle() { const title = document.querySelector('title')?.textContent?.trim() || ''; const length = title.length; if (!title) { return { check: 'Title Tag', status: 'fail', message: 'Missing title tag entirely' }; } if (length < 30) { return { check: 'Title Tag', status: 'warn', message: `Title is only ${length} chars, likely too short` }; } if (length > 60) { return { check: 'Title Tag', status: 'warn', message: `Title is ${length} chars, may get truncated` }; } return { check: 'Title Tag', status: 'pass', message: `Title length OK (${length} chars)` }; } function checkMetaDescription() { const meta = document.querySelector('meta[name="description"]'); const content = meta?.getAttribute('content')?.trim() || ''; const length = content.length; if (!content) { return { check: 'Meta Description', status: 'fail', message: 'Missing meta description' }; } if (length < 70 || length > 160) { return { check: 'Meta Description', status: 'warn', message: `Description is ${length} chars, outside 70-160 range` }; } return { check: 'Meta Description', status: 'pass', message: `Description length OK (${length} chars)` }; }
This connects directly to the rules covered in our heading structure best practices guide: one H1, and no skipped levels when opening a new section.
function checkHeadings() { const h1s = document.querySelectorAll('h1'); const issues = []; if (h1s.length === 0) { issues.push('No H1 found on page'); } else if (h1s.length > 1) { issues.push(`Multiple H1 tags found (${h1s.length})`); } // Check for skipped levels when opening a new section const allHeadings = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')]; let lastLevel = 0; allHeadings.forEach(heading => { const level = parseInt(heading.tagName[1]); if (lastLevel > 0 && level > lastLevel + 1) { issues.push(`Skipped from H${lastLevel} to H${level}: "${heading.textContent.trim().slice(0, 40)}"`); } lastLevel = level; }); return { check: 'Heading Structure', status: issues.length === 0 ? 'pass' : 'fail', message: issues.length === 0 ? 'Heading hierarchy looks clean' : issues.join('; ') }; }
Watch out for skipped levels when closing a subsection, that's actually valid per the W3C. An H4 closing a deep subsection can validly be followed by an H2 opening a new one. The check above only flags a jump when opening, tightening this rule further would produce false positives on perfectly correct pages.
Wellows' 2026 audit checklist flags missing alt text as one of the most common, easily fixable issues on real sites. The canonical check confirms exactly one canonical tag exists, echoing the failure mode covered in our canonical tag implementation guide.
function checkImageAltText() { const images = [...document.querySelectorAll('img')]; const missingAlt = images.filter(img => !img.getAttribute('alt')); return { check: 'Image Alt Text', status: missingAlt.length === 0 ? 'pass' : 'warn', message: missingAlt.length === 0 ? `All ${images.length} images have alt text` : `${missingAlt.length} of ${images.length} images missing alt text` }; } function checkCanonical() { const canonicals = document.querySelectorAll('link[rel="canonical"]'); if (canonicals.length === 0) { return { check: 'Canonical Tag', status: 'fail', message: 'No canonical tag found' }; } if (canonicals.length > 1) { return { check: 'Canonical Tag', status: 'fail', message: `${canonicals.length} canonical tags found, should be exactly 1` }; } return { check: 'Canonical Tag', status: 'pass', message: 'Single canonical tag present' }; }
Combine every check into one runner function that returns a structured report with a numeric score, weighting a failed check more heavily than a warning.
function runSeoAudit() { const results = [ checkTitle(), checkMetaDescription(), checkHeadings(), checkImageAltText(), checkCanonical() ]; let score = 100; results.forEach(result => { if (result.status === 'fail') score -= 20; if (result.status === 'warn') score -= 10; }); return { score: Math.max(0, score), results: results }; } // Run it and log a readable report const audit = runSeoAudit(); console.log(`SEO Score: ${audit.score}/100`); console.table(audit.results);
Wrapping the audit in a self-executing function and minifying it into a bookmarklet lets you run it on any page with one click, without opening DevTools manually.
javascript:(function(){ // ...paste the checkTitle, checkMetaDescription, checkHeadings, // checkImageAltText, checkCanonical, and runSeoAudit functions here... const audit = runSeoAudit(); let report = `SEO Score: ${audit.score}/100\n\n`; audit.results.forEach(r => { report += `${r.status.toUpperCase()}: ${r.check} — ${r.message}\n`; }); alert(report); })();
A DIY DOM-based tool has real limits worth understanding before relying on it.
| Factor | DOM-Based JS Tool (this guide) | Full Crawler (Screaming Frog, Ahrefs) |
|---|---|---|
| Scope | One page at a time, current DOM only | Entire site, hundreds of thousands of URLs |
| Setup cost | None, runs in any browser console | Software install or paid subscription |
| Server-side signals | Can't see HTTP headers, status codes | Full access to headers, redirects, response codes |
| Best use case | Quick spot-checks, learning, CI integration | Comprehensive audits, migrations, ongoing monitoring |
A short list of natural next checks to add once the basics are working.
Add an Open Graph tag check, confirming og:title, og:description, and og:image are all present.
Check for structured data, scanning for at least one script[type="application/ld+json"] block.
Verify hreflang consistency, on multilingual pages, for return tags on every language variant.
Wrap it as a Node.js script using a headless browser like Puppeteer for JavaScript-rendered pages and batch URLs.
Export results as JSON, making the tool usable in a CI pipeline as an automated regression check.
Select realistic values to see how the scoring logic above would grade a page.
Select the option that matches your page
No. A DOM-based tool only sees the current rendered page, it can't check HTTP status codes, response headers, or crawl an entire site. It's best for quick spot-checks, not comprehensive site audits.
Per W3C guidance, skipping is only a problem when opening a new section, like jumping from H2 straight to H4. It's valid when closing a subsection, so a strict "never skip" rule would create false positives.
Not directly from a browser console due to CORS restrictions. For auditing other URLs, wrap the same check functions in a Node.js script using a headless browser like Puppeteer.
Save the combined script as a bookmarklet. Once set up, clicking the bookmark runs the full audit against whatever page you're currently viewing, with no DevTools required.
For learning, quick checks, or CI integration, yes. For comprehensive, site-wide audits covering hundreds of pages, HTTP-level signals, and ongoing monitoring, a dedicated crawler tool remains more practical.
Five focused checks cover most of what a lightweight audit needs
Each check returns a structured pass/warn/fail result
Heading checks should only flag skips when opening, not closing
A weighted score combines all checks into one number
Bookmarklets make the tool usable on any page instantly
DOM tools complement, but don't replace, full site crawlers
This audit tool pairs naturally with the heading structure and canonical guides its checks are based on. Explore both next.









