Roier SEO - Technical SEO Auditor & Fixer
An AI-powered SEO optimization skill that audits websites and automatically implements fixes.
When to Use
- User asks to "audit my site" or "check SEO"
- User wants to "improve performance" or "fix SEO issues"
- User mentions "lighthouse", "pagespeed", or "core web vitals"
- User wants to add/fix meta tags, structured data, or accessibility
- User has a local dev server and wants SEO analysis
Quick Start
1. Run an Audit
For a live website:
node ~/.claude/skills/roier-seo/scripts/audit.js https://example.com
For a local dev server (must be running):
node ~/.claude/skills/roier-seo/scripts/audit.js http://localhost:3000
2. Analyze Results
After running the audit, analyze the JSON output to identify issues and prioritize fixes.
3. Implement Fixes
Use the fix patterns in this skill to automatically implement SEO improvements in the user's codebase.
Audit Categories
The audit returns scores (0-100) for five categories:
| Category | Description | Weight |
|---|---|---|
| Performance | Page load speed, Core Web Vitals | High |
| Accessibility | WCAG compliance, screen reader support | High |
| Best Practices | Security, modern web standards | Medium |
| SEO | Search engine optimization, crawlability | High |
| PWA | Progressive Web App compliance | Low |
Technical SEO Fix Patterns
Meta Tags (HTML Head)
Missing or Bad Title Tag
<!-- Bad -->
<title>Home</title>
<!-- Good -->
<title>Primary Keyword - Secondary Keyword | Brand Name</title>
Rules:
- 50-60 characters max
- Include primary keyword near the beginning
- Unique per page
- Include brand name at end
Missing or Bad Meta Description
<!-- Add to <head> -->
<meta name="description" content="Compelling description with keywords. 150-160 characters that encourages clicks from search results.">
Rules:
- 150-160 characters
- Include primary and secondary keywords naturally
- Compelling call-to-action
- Unique per page
Missing Viewport Meta Tag
<meta name="viewport" content="width=device-width, initial-scale=1">
Missing Charset
<meta charset="UTF-8">
Missing Language
<html lang="en">
Open Graph Tags (Social Sharing)
<meta property="og:title" content="Page Title">
<meta property="og:description" content="Page description">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Brand Name">
Twitter Card Tags
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Page Title">
<meta name="twitter:description" content="Page description">
<meta name="twitter:image" content="https://example.com/image.jpg">
Canonical URL
<link rel="canonical" href="https://example.com/canonical-page">
Robots Meta
<!-- Allow indexing (default) -->
<meta name="robots" content="index, follow">
<!-- Prevent indexing (for staging, admin pages) -->
<meta name="robots" content="noindex, nofollow">
Structured Data (JSON-LD)
Website Schema
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Site Name",
"url": "https://example.com",
"potentialAction": {
"@type": "SearchAction",
"target": "https://example.com/search?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}
</script>
Organization Schema
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Company Name",
"url": "https://example.com",
"logo": "https://example.com/logo.png",
"sameAs": [
"https://twitter.com/company",
"https://linkedin.com/company/company"
]
}
</script>
BreadcrumbList Schema
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com"},
{"@type": "ListItem", "position": 2, "name": "Category", "item": "https://example.com/category"},
{"@type": "ListItem", "position": 3, "name": "Page"}
]
}
</script>
Article Schema (for blog posts)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Article Title",
"author": {"@type": "Person", "name": "Author Name"},
"datePublished": "2024-01-15",
"dateModified": "2024-01-20",
"image": "https://example.com/article-image.jpg",
"publisher": {
"@type": "Organization",
"name": "Publisher Name",
"logo": {"@type": "ImageObject", "url": "https://example.com/logo.png"}
}
}
</script>
Performance Optimizations
Image Optimization
Add width/height attributes (prevent CLS)
<!-- Bad -->
<img src="image.jpg" alt="Description">
<!-- Good -->
<img src="image.jpg" alt="Description" width="800" height="600">
Add lazy loading
<img src="image.jpg" alt="Description" loading="lazy">
Use modern formats (WebP/AVIF)
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Description">
</picture>
Font Optimization
Preload critical fonts
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
Use font-display: swap
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
}
Resource Hints
<!-- Preconnect to critical third-party origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com">
<!-- DNS prefetch for non-critical origins -->
<link rel="dns-prefetch" href="https://analytics.example.com">
<!-- Preload critical resources -->
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/hero-image.webp" as="image">
Accessibility Fixes
Missing Alt Text
<!-- Bad -->
<img src="photo.jpg">
<!-- Good (descriptive) -->
<img src="photo.jpg" alt="Team members collaborating in the office">
<!-- Good (decorative, intentionally empty) -->
<img src="decoration.jpg" alt="" role="presentation">
Insufficient Color Contrast
Ensure text has at least:
- 4.5:1 contrast ratio for normal text
- 3:1 contrast ratio for large text (18px+ or 14px+ bold)
Missing Form Labels
<!-- Bad -->
<input type="email" placeholder="Email">
<!-- Good -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email">
Missing Skip Link
<!-- Add as first element in body -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Style to hide visually but accessible -->
<style>
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
left: 0;
top: 0;
z-index: 9999;
background: #000;
color: #fff;
padding: 8px 16px;
}
</style>
Missing Landmark Roles
<header role="banner">...</header>
<nav role="navigation">...</nav>
<main role="main" id="main-content">...</main>
<footer role="contentinfo">...</footer>
Button Accessibility
<!-- Bad -->
<div onclick="submit()">Submit</div>
<!-- Good -->
<button type="submit">Submit</button>
<!-- Icon button needs aria-label -->
<button aria-label="Close menu">
<svg>...</svg>
</button>
Best Practices Fixes
HTTPS
Ensure all resources load over HTTPS. Fix mixed content:
<!-- Bad -->
<img src="http://example.com/image.jpg">
<!-- Good -->
<img src="https://example.com/image.jpg">