Skip to main content

Command Palette

Search for a command to run...

Build a TypeScript SEO Content Checker Tool: Catch SEO Bugs Before They Ship

Updated
7 min read
Build a TypeScript SEO Content Checker Tool: Catch SEO Bugs Before They Ship
M
I build websites and apps using Javascript. I love making things work fast and look great. Let's create something cool

I spent an embarrassing amount of time wondering why a client's blog posts kept dropping in rankings after every deploy. No design changes. No content edits. Just a routine refactor, and suddenly half the pages were missing their canonical tags. The culprit? A copy-paste error in a template component that nobody caught because there was no automated check. Building a TypeScript SEO content checker tool directly into the development workflow fixed that permanently. Here's exactly how to do it.

What Is a TypeScript SEO Content Checker Tool and Why Do You Need One?

A TypeScript SEO content checker tool is a script (or library) that programmatically validates your HTML pages for SEO issues: missing meta tags, keyword density problems, thin content, and broken structured data, all before anything reaches production.

The standard approach to SEO is reactive: ship, wait for Google Search Console to flag something, then fix it. That loop can take weeks. By the time you notice a canonical tag is missing or a meta description is empty, the damage is done.

The smarter move is to treat SEO metadata the same way you treat type safety: validate it at build time or in CI, before any page goes live. A free JavaScript SEO check costs you nothing to add to your pipeline and can save hours of post-deploy firefighting.

Step 1: Build the Core TypeScript SEO Checker

Here is what a complete, copy-paste-ready TypeScript SEO content checker looks like when run against raw HTML:

interface SEOCheckResult {
  passed: boolean;
  issues: string[];
  score: number;
}

interface SEOMetadata {
  title?: string;
  description?: string;
  canonical?: string;
  ogTitle?: string;
  ogDescription?: string;
}

function extractMetadata(html: string): SEOMetadata {
  const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
  const descMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)["']/i);
  const canonicalMatch = html.match(/<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']+)["']/i);
  const ogTitleMatch = html.match(/<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']+)["']/i);
  const ogDescMatch = html.match(/<meta[^>]+property=["']og:description["'][^>]+content=["']([^"']+)["']/i);

  return {
    title: titleMatch?.[1],
    description: descMatch?.[1],
    canonical: canonicalMatch?.[1],
    ogTitle: ogTitleMatch?.[1],
    ogDescription: ogDescMatch?.[1],
  };
}

function checkSEO(html: string): SEOCheckResult {
  const meta = extractMetadata(html);
  const issues: string[] = [];

  if (!meta.title) issues.push("Missing <title> tag");
  else if (meta.title.length > 60) issues.push(`Title too long (${meta.title.length} chars, max 60)`);
  else if (meta.title.length < 30) issues.push(`Title too short (${meta.title.length} chars, min 30)`);

  if (!meta.description) issues.push("Missing meta description");
  else if (meta.description.length > 160) issues.push(`Meta description too long (${meta.description.length} chars)`);

  if (!meta.canonical) issues.push("Missing canonical tag");
  if (!meta.ogTitle) issues.push("Missing og:title");
  if (!meta.ogDescription) issues.push("Missing og:description");

  const score = Math.max(0, 100 - issues.length * 15);

  return { passed: issues.length === 0, issues, score };
}

// Usage
const html = `
  <html>
    <head>
      <title>My Blog Post About TypeScript</title>
      <meta name="description" content="Learn TypeScript in 10 minutes." />
    </head>
  </html>
`;

const result = checkSEO(html);
console.log(`SEO Score: ${result.score}/100`);
console.log("Issues:", result.issues);
// Output:
// SEO Score: 55/100
// Issues: [ 'Missing canonical tag', 'Missing og:title', 'Missing og:description' ]

Drop this into your project and you immediately have a reusable TypeScript SEO validator. Hook it into your test suite and it will catch regressions before they ship.

Step 2: Add Keyword Density and Content Quality Checks

Missing tags are the obvious failures. But content-level SEO problems (thin copy, keyword stuffing, low readability) are sneakier. A complete TypeScript SEO content checker tool should flag those too:

interface ContentAnalysis {
  wordCount: number;
  keywordDensity: number;
  headingCount: number;
  imageAltMissing: number;
}

function analyzeContent(html: string, targetKeyword: string): ContentAnalysis {
  // Strip tags and count words
  const textContent = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
  const words = textContent.split(" ").filter(Boolean);
  const wordCount = words.length;

  // Keyword density
  const keywordPattern = new RegExp(targetKeyword, "gi");
  const keywordMatches = textContent.match(keywordPattern) ?? [];
  const keywordDensity = parseFloat(((keywordMatches.length / wordCount) * 100).toFixed(2));

  // Count headings
  const headingMatches = html.match(/<h[1-6][^>]*>/gi) ?? [];
  const headingCount = headingMatches.length;

  // Images without alt text
  const allImages = html.match(/<img[^>]+>/gi) ?? [];
  const imagesWithoutAlt = allImages.filter(img => !img.includes("alt=")).length;

  return {
    wordCount,
    keywordDensity,
    headingCount,
    imageAltMissing: imagesWithoutAlt,
  };
}

function flagContentIssues(analysis: ContentAnalysis, keyword: string): string[] {
  const warnings: string[] = [];

  if (analysis.wordCount < 300) warnings.push(`Content too thin (${analysis.wordCount} words, aim for 300+)`);
  if (analysis.keywordDensity > 3) warnings.push(`Keyword "\({keyword}" may be over-used (\){analysis.keywordDensity}%)`);
  if (analysis.keywordDensity === 0) warnings.push(`Keyword "${keyword}" not found in content`);
  if (analysis.headingCount === 0) warnings.push("No headings found. Add H2/H3 structure");
  if (analysis.imageAltMissing > 0) warnings.push(`${analysis.imageAltMissing} image(s) missing alt text`);

  return warnings;
}

This is vanilla TypeScript with no dependencies, runs anywhere. The flagContentIssues function gives you actionable, specific warnings instead of the vague "your SEO needs work" message most tools produce.

Step 3: Wire the SEO Content Checker Into Your CI Build

A TypeScript SEO content checker tool is only as valuable as how consistently it runs. The best place for it is a post-build step that scans every HTML file in your /dist folder and fails the build if issues are found:

import fs from "fs";
import path from "path";

async function auditBuildOutput(distDir: string): Promise<void> {
  const htmlFiles = fs.readdirSync(distDir).filter(f => f.endsWith(".html"));
  let totalIssues = 0;

  for (const file of htmlFiles) {
    const html = fs.readFileSync(path.join(distDir, file), "utf-8");
    const seo = checkSEO(html);
    const content = analyzeContent(html, "typescript"); // swap in your target keyword

    const allIssues = [...seo.issues, ...flagContentIssues(content, "typescript")];

    if (allIssues.length > 0) {
      console.warn(`\n⚠️  ${file}`);
      allIssues.forEach(issue => console.warn(`   - ${issue}`));
      totalIssues += allIssues.length;
    }
  }

  if (totalIssues > 0) {
    console.error(`\nBuild audit failed: ${totalIssues} SEO issue(s) found.`);
    process.exit(1); // Fails CI — nothing ships with broken SEO
  } else {
    console.log("\n✅ All pages passed SEO audit.");
  }
}

auditBuildOutput("./dist");

Add ts-node audit.ts as a postbuild script in your package.json. Your CI pipeline will now reject any deploy that introduces SEO regressions. This is the same principle behind linting: catching issues at the source, not in production.

When to Use a Pre-Built Free JavaScript SEO Library Instead

The patterns above cover a solid baseline for any TypeScript SEO content checker tool. But if you are working on a content-heavy site with dozens of pages and need structured scoring, batch processing, or a JSON report for your team, writing all of that yourself gets repetitive fast.

That is where a dedicated free JavaScript SEO library fits in. power-seo is one option worth knowing: it wraps the kind of logic shown above into a consistent API with scoring, structured output, and multi-page support out of the box. A detailed walkthrough comparing it to the roll-your-own approach is on ccbd.dev.

The decision rule is simple: build your own TypeScript SEO checker if you need tight control over what gets validated and why. Reach for an existing package if you want something running in your project this afternoon.

Key Takeaways

  • SEO bugs are regular bugs. Type checking, unit tests, and CI gates belong on your metadata just as much as on your business logic. A TypeScript SEO content checker tool enforces this automatically.

  • The most common SEO failures are the most avoidable: missing canonical tags, meta descriptions over 160 characters, and blank Open Graph tags on pages that were "just quick posts."

  • Content checks matter as much as tag checks. A page with a perfect <head> and 80 words of body copy will still underperform. Keyword density and word count belong in your automated audit.

  • process.exit(1) is your best SEO tool. Failing the CI build on SEO issues sounds aggressive until the first time it saves you from a bad deploy.

If you want to try this approach, here's the repo: https://github.com/CyberCraftBD/power-seo