DEV Community

Cover image for 6 Open Source SEO Tools Developers Actually Use in CI (With Working Code)
Mitu Das
Mitu Das

Posted on Originally published at ccbd.dev

6 Open Source SEO Tools Developers Actually Use in CI (With Working Code)

I once spent an entire afternoon debugging why Google couldn't see my React app. The fix was 4 lines of code: a missing robots meta tag was quietly blocking the whole /blog route. That bug taught me something: SEO isn't a marketing problem, it's an engineering problem, and there's a real open source SEO tools stack for solving it, no paid dashboard required.

Quick answer: the best open source SEO tools for developers, by job:

  • Lighthouse CI: official Google tool, runs SEO/perf/a11y audits per PR
  • Unlighthouse: runs Lighthouse across your entire site, not just one URL
  • broken-link-checker: crawls a site for dead links
  • sitemap: generates XML sitemaps from your actual routes
  • schema-dts + a JSON-LD validator: catches broken structured data
  • power-seo: bundles checks like the above into one CI command

Here's how each one actually works, with code.

Best Open Source SEO Tools at a Glance

If you're comparing open source SEO tools for a CI setup, here's how these six stack up:

Tool Checks Runs As Best For
Lighthouse CI Perf, SEO, a11y score CLI / GitHub Action Auditing one URL on every PR
Unlighthouse Same, site-wide CLI Catching issues on pages nobody remembers to test
broken-link-checker Dead internal/external links Node CLI/library Link rot before Google finds it
sitemap XML sitemap generation Node library Keeping sitemap.xml in sync with routes
schema-dts + validator JSON-LD correctness TS types + script Structured data that silently breaks
power-seo Bundles the above CLI One command, one CI step

1. Lighthouse CI: the Official, Battle-Tested Auditor

Lighthouse is the engine behind Chrome DevTools' audit tab, and it's fully open source. Lighthouse CI wraps it so you can fail a build on score regressions instead of eyeballing a report once a quarter.

npm install -g @lhci/cli
Enter fullscreen mode Exit fullscreen mode
# lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/blog'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:seo': ['error', { minScore: 0.9 }],
      },
    },
    upload: { target: 'temporary-public-storage' },
  },
};
Enter fullscreen mode Exit fullscreen mode
lhci autorun
Enter fullscreen mode Exit fullscreen mode

Result: the run fails with a non-zero exit code the moment SEO score drops below 90. For example, a deploy that accidentally strips <meta name="viewport"> gets caught before merge, not after a client complains.

2. Unlighthouse: Auditing Every Page, Not Just the Homepage

Lighthouse CI is great, but you have to list every URL by hand. Unlighthouse crawls your live site and runs Lighthouse against every discovered page automatically, which is how you catch the one blog post from 2023 nobody's checked since.

npx unlighthouse --site https://yoursite.com
Enter fullscreen mode Exit fullscreen mode

That's it, no config file required for a first pass. It opens a local dashboard showing SEO/perf/a11y scores for every crawled URL, and you can pass --budget flags to fail CI on a site-wide threshold instead of a single page.

Result: on a 40-page marketing site, this surfaced 6 pages missing canonical tags that Lighthouse CI's manual URL list had simply never included.

3. broken-link-checker: Catching Link Rot Before Google Does

Broken internal links waste crawl budget and tank UX; broken outbound links quietly signal a stale, low-effort page. broken-link-checker crawls a site (or a single HTML string) and reports every dead link.

npm install broken-link-checker --save-dev
Enter fullscreen mode Exit fullscreen mode
// check-links.js
const { SiteChecker } = require('broken-link-checker');

const checker = new SiteChecker(
  { excludeExternalLinks: false },
  {
    link: (result) => {
      if (result.broken) {
        console.warn(`❌ Broken: ${result.url.original} (found on ${result.base.original})`);
      }
    },
    end: () => console.log('✅ Link check complete'),
  }
);

checker.enqueue('https://yoursite.com');
Enter fullscreen mode Exit fullscreen mode

Result: this caught three internal links pointing at a renamed /docs/v1/ path six months after a docs restructure. Search Console wouldn't have flagged it for weeks.

4. sitemap: Keeping Your Sitemap From Going Stale

Hand-written sitemaps rot. Generate the XML from your actual route list at build time so it can never drift.

npm install sitemap --save-dev
Enter fullscreen mode Exit fullscreen mode
// generate-sitemap.js
const { SitemapStream, streamToPromise } = require('sitemap');
const { createWriteStream } = require('fs');
const routes = require('./routes.json'); // your actual route list

async function generateSitemap() {
  const sitemap = new SitemapStream({ hostname: 'https://yoursite.com' });
  const writeStream = createWriteStream('./public/sitemap.xml');
  sitemap.pipe(writeStream);

  routes.forEach((route) => {
    sitemap.write({
      url: route.path,
      changefreq: route.changefreq || 'weekly',
      priority: route.priority || 0.5,
      lastmod: route.lastModified,
    });
  });

  sitemap.end();
  await streamToPromise(sitemap);
  console.log(`✅ Sitemap generated with ${routes.length} URLs`);
}

generateSitemap();
Enter fullscreen mode Exit fullscreen mode

Wire it into "prebuild": "node generate-sitemap.js" in package.json.

Result: caught 14 orphaned URLs still submitted to Search Console months after the pages were removed, pure crawl-budget waste.

5. schema-dts + a JSON-LD Validator: Structured Data That Doesn't Silently Break

Broken JSON-LD doesn't throw a JS error or break your UI. It just fails to earn rich results. schema-dts gives you TypeScript types for schema.org so a missing required field is a compile error, not a runtime mystery. Pair it with a runtime check for anything rendered outside TypeScript's reach:

npm install schema-dts --save-dev
npm install jsdom --save-dev
Enter fullscreen mode Exit fullscreen mode
// validate-structured-data.js
const { JSDOM } = require('jsdom');

function validateJsonLd(html) {
  const dom = new JSDOM(html);
  const scripts = dom.window.document.querySelectorAll('script[type="application/ld+json"]');

  if (scripts.length === 0) {
    console.warn('⚠️  No structured data found on this page');
    return;
  }

  scripts.forEach((script, i) => {
    try {
      const data = JSON.parse(script.textContent);
      if (!data['@type']) console.warn(`⚠️  Block ${i}: missing @type`);
      else console.log(`✅ Block ${i}: valid @type "${data['@type']}"`);
    } catch (e) {
      console.error(`❌ Block ${i}: invalid JSON: ${e.message}`);
    }
  });
}

const html = require('fs').readFileSync('./rendered-page.html', 'utf8');
validateJsonLd(html);
Enter fullscreen mode Exit fullscreen mode

For a spec-complete check, cross-reference against Google's Rich Results Test too. This script catches malformed JSON, not every schema.org requirement.

Result: caught a template outputting undefined into a JSON-LD block when a product's price field was missing, silently breaking the whole block, not just that field.

6. power-seo: Bundling All of the Above Into One CI Step

Running five separate scripts works until someone forgets to run one. power-seo bundles checks like the ones above (missing meta tags, duplicate titles, broken canonicals, orphaned sitemap entries) into a single CLI command for CI.

# .github/workflows/seo-check.yml
name: SEO Audit
on: [pull_request]

jobs:
  seo-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install
      - run: npx power-seo audit --url http://localhost:3000 --fail-on-warning
Enter fullscreen mode Exit fullscreen mode

Disclosure: I work with the team behind this package, so take the recommendation with that context. The code above is real, MIT-licensed, and you can read every check it runs before trusting it with your build. The value isn't the tool itself, it's that the check runs on every PR instead of when someone remembers. You could wire the same pipeline yourself from tools 1–5; power-seo just saves the glue code. Longer writeup on the reasoning is on ccbd.dev if you want it.

What I Learned

  • Single-page tools (Lighthouse CI) and site-wide crawlers (Unlighthouse) catch different bugs, so use both, not one instead of the other.
  • Link rot and broken structured data are both silent failures. Neither throws an error; both need a dedicated check.
  • Automate the sitemap. A hand-maintained one is a lie waiting to happen.
  • None of this replaces off-page tools (backlinks, keyword volume). It just closes the technical-SEO gaps that are 100% within a developer's control.
  • If you only adopt one thing from this list, make it CI enforcement, that's what turns "open source SEO tools" from a bookmarked list into something that actually protects your rankings.

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

Let's Talk

Which of these have you actually used, and which open source SEO tool should've been on this list? I'll start: still looking for a good open source alternative to Screaming Frog's UI-based crawl reports. Drop your pick below.

Top comments (0)