npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@capyseo/core

v0.1.2

Published

Framework-agnostic SEO analysis engine with 50+ rules and multi-provider AI integration

Readme

@capyseo/core

Framework-agnostic SEO analysis engine with 50+ rules and multi-provider AI integration.

Capyseo Core

npm version License: MIT TypeScript

Documentation · GitHub · Report Issue

Part of the Capyseo toolkit.


Overview

The core analysis engine for Capyseo—a powerful, framework-agnostic library for analyzing HTML and identifying SEO issues. Use it programmatically in your build tools, CI/CD pipelines, or custom applications.


Features

  • 50+ SEO Rules — Meta tags, images, headings, technical SEO, social meta, mobile, security, content
  • Multi-Provider AI — Gemini, OpenAI, Anthropic, Ollama for suggestions and autofixes
  • Framework Agnostic — Works with any HTML, integrates with any build tool
  • Cheerio-Based — Fast, reliable HTML parsing without browser overhead
  • Progress Callbacks — Track analysis in real-time
  • Custom Rules — Add your own SEO checks with simple API
  • Multiple Reporters — Console, JSON, HTML, CSV, SARIF output formats

Installation

# Using npm
npm install @capyseo/core

# Using Bun
bun add @capyseo/core

# Using pnpm
pnpm add @capyseo/core

Quick Start

Basic Analysis

Analyze a single page:

import { SEOAnalyzer } from '@capyseo/core';

const analyzer = new SEOAnalyzer();

const html = await fetch('https://example.com').then(r => r.text());
const report = await analyzer.analyzePage(html, 'https://example.com');

console.log(`Score: ${report.score}/100`);

for (const issue of report.issues) {
  console.log(`[${issue.severity}] ${issue.message}`);
}

Output:

Score: 85/100

[error] Missing meta description
[warning] Missing og:image
[info] Consider adding structured data

AI-Powered Analysis

Enable AI suggestions with any provider:

const analyzer = new SEOAnalyzer({
  aiProvider: 'gemini',
  aiApiKey: process.env.GEMINI_API_KEY,
});

const report = await analyzer.analyzePage(html, url);

// Get AI-generated suggestions
for (const issue of report.issues) {
  if (issue.aiSuggestion) {
    console.log(`💡 ${issue.aiSuggestion}`);
  }
}

Supported AI Providers:

| Provider | Model | API Key Required | Notes | |----------|-------|------------------|-------| | Gemini | gemini-2.5-flash | Yes | Best price-performance (recommended) | | OpenAI | gpt-4o | Yes | High quality multimodal | | Anthropic | claude-sonnet-4-5 | Yes | Hybrid reasoning, excellent for analysis | | Ollama | deepseek-r1, qwen3-coder | No | Free, runs locally |


Multi-Page Analysis

Analyze entire sites with progress tracking:

const analyzer = new SEOAnalyzer({
  onProgress: (event) => {
    if (event.type === 'page_start') {
      console.log(`Analyzing ${event.url} (${event.pageNumber}/${event.totalPages})`);
    }
  },
});

const urls = [
  'https://example.com/',
  'https://example.com/about',
  'https://example.com/blog',
];

const fetcher = async (url: string) => {
  const res = await fetch(url);
  return res.text();
};

for await (const report of analyzer.analyzeSite(urls, fetcher)) {
  console.log(`${report.url}: ${report.score}/100`);
}

Custom Rules

Add your own SEO checks:

import type { SEORule } from '@capyseo/core';

const customRule: SEORule = {
  id: 'custom-canonical',
  name: 'Canonical URL Required',
  description: 'Ensures every page has a canonical URL',
  category: 'technical',
  severity: 'error',

  async check($, url, context) {
    const issues = [];

    const canonical = $('link[rel="canonical"]').attr('href');

    if (!canonical) {
      issues.push({
        rule: 'custom-canonical',
        severity: 'error',
        message: 'Missing canonical URL',
        recommendation: `Add <link rel="canonical" href="${url}">`,
      });
    }

    return issues;
  },
};

analyzer.addRule(customRule);

Output Reporters

Format reports for different needs:

import {
  consoleReporter,
  jsonReporter,
  htmlReporter,
  sarifReporter,
  csvReporter,
} from '@capyseo/core/reporters';

const reports = [report1, report2, report3];

// Human-readable console output
console.log(consoleReporter(reports));

// JSON for APIs/scripts
const json = jsonReporter(reports);
fs.writeFileSync('report.json', json);

// HTML report for viewing in browser
const html = htmlReporter(reports);
fs.writeFileSync('report.html', html);

// SARIF for GitHub Code Scanning
const sarif = sarifReporter(reports);
fs.writeFileSync('report.sarif', sarif);

// CSV for spreadsheet analysis
const csv = csvReporter(reports);
fs.writeFileSync('report.csv', csv);

API Reference

SEOAnalyzer

Main analysis class.

const analyzer = new SEOAnalyzer(options?: AnalyzerOptions);

Options

interface AnalyzerOptions {
  // Custom rules (default: all built-in rules)
  rules?: SEORule[];

  // AI provider: 'gemini' | 'openai' | 'anthropic' | 'ollama'
  aiProvider?: string;

  // API key for the AI provider
  aiApiKey?: string;

  // Specific AI model to use
  aiModel?: string;

  // Fetch external resources for validation (default: true)
  liveChecks?: boolean;

  // Include Lighthouse metrics (requires Playwright)
  lighthouse?: boolean;

  // Throw errors on rule failures (default: false)
  strictMode?: boolean;

  // AI response cache duration (ms)
  cacheTTL?: number;

  // Progress callback
  onProgress?: (event: ProgressEvent) => void;
}

Methods

analyzePage(html: string, url: string): Promise<Report>

Analyze a single HTML page.

const report = await analyzer.analyzePage(html, 'https://example.com');

analyzeSite(urls: string[], fetcher: Fetcher): AsyncIterableIterator<Report>

Analyze multiple pages.

const fetcher = async (url) => {
  const res = await fetch(url);
  return res.text();
};

for await (const report of analyzer.analyzeSite(urls, fetcher)) {
  console.log(report.url, report.score);
}

addRule(rule: SEORule): void

Add a custom rule.

analyzer.addRule(myCustomRule);

removeRule(ruleId: string): void

Remove a rule by ID.

analyzer.removeRule('meta-title');

Types

interface Report {
  url: string;
  score: number;
  issues: Issue[];
  metadata?: PageMetadata;
  lighthouse?: LighthouseMetrics;
}

interface Issue {
  rule: string;
  severity: 'error' | 'warning' | 'info';
  message: string;
  recommendation?: string;
  aiSuggestion?: string;
  location?: Location;
}

interface PageMetadata {
  title?: string;
  description?: string;
  canonical?: string;
  robots?: string;
  ogData?: Record<string, string>;
  twitterData?: Record<string, string>;
}

Full type definitions: Documentation


Built-in Rules

Capyseo Core includes 50+ SEO rules across multiple categories:

Meta Tags (10 rules)

  • Meta title presence and length
  • Meta description presence and length
  • Canonical URLs
  • Robots meta tags
  • Viewport configuration
  • Language declarations

Images (6 rules)

  • Alt text for all images
  • Image file sizes
  • Lazy loading
  • Next-gen formats (WebP, AVIF)
  • Responsive images

Headings (5 rules)

  • Single H1 per page
  • Proper heading hierarchy
  • No empty headings
  • Keyword presence

Content (8 rules)

  • Minimum content length
  • Readability scores
  • Keyword density
  • Internal link presence
  • Paragraph length

Social Meta (6 rules)

  • Open Graph tags (og:title, og:description, og:image, og:url, og:type)
  • Twitter Card tags

Technical SEO (7 rules)

  • HTTPS usage
  • Broken links
  • Redirect chains
  • URL structure
  • Sitemap presence
  • Robots.txt validation

Mobile (4 rules)

  • Mobile viewport
  • Touch target sizes
  • Font sizes
  • Tap delay

Security (3 rules)

  • HTTPS enforcement
  • Secure cookie attributes
  • Content Security Policy

Performance (1 rule)

  • Page size limits

Full rule reference: Documentation


Examples

Next.js Integration

// scripts/seo-check.ts
import { SEOAnalyzer } from '@capyseo/core';
import { consoleReporter } from '@capyseo/core/reporters';
import fs from 'fs/promises';
import path from 'path';

const analyzer = new SEOAnalyzer();

const outDir = path.join(process.cwd(), 'out');
const files = await fs.readdir(outDir, { recursive: true });

const htmlFiles = files.filter(f => f.endsWith('.html'));
const reports = [];

for (const file of htmlFiles) {
  const html = await fs.readFile(path.join(outDir, file), 'utf-8');
  const url = `https://example.com/${file.replace(/index\.html$/, '')}`;

  const report = await analyzer.analyzePage(html, url);
  reports.push(report);
}

console.log(consoleReporter(reports));

// Fail if average score is too low
const avg = reports.reduce((sum, r) => sum + r.score, 0) / reports.length;
if (avg < 80) {
  process.exit(1);
}

Custom Reporter

function slackReporter(reports: Report[]): string {
  const avg = reports.reduce((sum, r) => sum + r.score, 0) / reports.length;
  const emoji = avg >= 90 ? '🟢' : avg >= 70 ? '🟡' : '🔴';

  const issues = reports.flatMap(r => r.issues);
  const errors = issues.filter(i => i.severity === 'error');

  return JSON.stringify({
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: `${emoji} SEO Report: ${avg.toFixed(1)}/100`,
        },
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*${reports.length}* pages analyzed\n*${errors.length}* errors found`,
        },
      },
    ],
  });
}

Related Packages

| Package | Description | |---------|-------------| | @capyseo/cli | Command-line interface for analyzing any site | | @capyseo/sveltekit | SvelteKit integration with Vite plugin & hooks |


Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines.


License

MIT © Capyseo


Support