From antigravity-awesome-skills
Framework-agnostic SEO workflow for sites with many tool/product pages. Covers duplicate content, meta tags, heading hierarchy, internal linking, URL slugs, E-E-A-T, content registry pattern for scaling 50–500 pages, and blog content strategy for position 50–68 keywords.
How this skill is triggered — by the user, by Claude, or both
Slash command
/antigravity-awesome-skills:tools-page-seo-optimizerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert in technical SEO and content strategy for sites with large collections of tool, product, or feature pages. Your workflow is framework-agnostic — applies to Django, Rails, Laravel, Express, Next.js, Nuxt, Astro, WordPress, and static HTML.
You are an expert in technical SEO and content strategy for sites with large collections of tool, product, or feature pages. Your workflow is framework-agnostic — applies to Django, Rails, Laravel, Express, Next.js, Nuxt, Astro, WordPress, and static HTML.
Derived from a real audit that found 93 of 105 tool pages sharing identical template prose and ranking at average position 68. This skill is the playbook that fixes it.
Full audit from scratch? → Run all phases in order
All tool pages rank the same? → Phase 2 (Content Registry) first
Meta titles/descriptions all generic? → Phase 1 (Meta Tags)
Tool pages buried / hard to navigate? → Phase 5 (Internal Linking)
Bad URL slugs? → Phase 6 (URL Slug Hygiene)
Site looks authorless to Google? → Phase 7 (E-E-A-T)
Stuck at position 50–68 on keywords? → Phase 9 (Blog Content Strategy)
Fixes deployed but unsure they're live? → Phase 10 (Live Verification)
Before writing any code, locate these in the codebase. Names vary by framework — adapt.
| What to find | Common locations |
|---|---|
| URL routing | routes.rb, urls.py, routes/, pages/, app/ |
| Head / meta template | _head.html, layout.js, base.html, app.blade.php |
| Tool/page registry | config file, database seed, JSON, lib/guides.js, data/tools.js |
Answer these before writing a single line:
<title>, <meta name="description">, <h1>?Every tool page sharing the same <title> template with only the tool name swapped in
is the single most common reason tool sites rank poorly. Google treats near-identical titles
as duplicate pages and demotes all of them.
{Tool Name} | {Specific Outcome} — {Brand}
| ✅ Good | ❌ Bad |
|---|---|
Meta Tag Generator | Create Perfect SEO Titles Free — MySite | Meta Tag Generator - MySite Tools |
Broken Link Finder | Scan Any Page for Dead URLs — MySite | Broken Link Finder - Free Online Tool | MySite |
Rules:
{What it does — one action sentence}. {Key differentiator}. {CTA}.
Example: Scan any webpage for broken links in seconds. Checks internal and external URLs, exports results as CSV. Free, no account needed.
Rules:
<!-- Generic template pattern -->
<title>{{ tool.meta_title | default(tool.name + " | " + site_name) }}</title>
<meta name="description" content="{{ tool.meta_description | default(tool.tagline) }}">
# validate_meta.py
import json, sys
tools = json.load(open('data/tools.json'))
errors = []
for t in tools:
slug = t.get('slug', '?')
title = t.get('meta_title', '')
desc = t.get('meta_description', '')
if not title: errors.append(f"MISSING TITLE: {slug}")
elif len(title) > 60: errors.append(f"TITLE TOO LONG ({len(title)}): {slug}")
if not desc: errors.append(f"MISSING DESC: {slug}")
elif len(desc) < 120: errors.append(f"DESC TOO SHORT ({len(desc)}): {slug}")
elif len(desc) > 160: errors.append(f"DESC TOO LONG ({len(desc)}): {slug}")
if errors:
print('\n'.join(errors)); sys.exit(1)
print(f"✅ All {len(tools)} tools passed meta validation")
Root cause of poor rankings on tool sites: 80–95% of tool pages share identical template prose. Google sees them as thin, near-duplicate pages and ranks none well. Fix this before anything else.
# Find shared prose in your templates — if these strings appear in a shared template
# file, you have the problem
D1=$(grep -rn "powerful tool that helps" templates/ src/ 2>/dev/null | head -5)
[ -n "$D1" ] && echo " ✗ Shared template prose found" || echo " ✓ No shared prose"
D2=$(grep -rn "easy to use" templates/ src/ 2>/dev/null | head -5)
[ -n "$D2" ] && echo " ✗ Template filler found"
# data/tools/meta-tag-generator.yaml (or JSON, DB columns, JS object — adapt to your stack)
slug: meta-tag-generator
name: Meta Tag Generator
meta_title: "Meta Tag Generator | Create Perfect SEO Titles & Descriptions Free"
meta_description: "Generate optimized title tags and meta descriptions with live character
counters. Enforces Google's 60/160 char limits. Instant, free, no account needed."
introduction: >
The meta tag generator creates the two most critical on-page SEO elements —
your title tag and meta description — with live character counters that enforce
Google's recommended limits before you publish. [80+ unique words minimum]
best_practices:
- "Include your primary keyword within the first 40 characters of the title"
- "Write a unique description per page — duplicate descriptions waste crawl budget"
- "Use action verbs in descriptions: Generate, Find, Check, Analyze"
how_to_steps:
- name: "Enter your page details"
text: "Type your target keyword, page topic, and a brief summary of the content"
- name: "Check the live character counters"
text: "Keep title ≤60 chars and description ≤160 chars"
- name: "Copy and paste the output"
text: "Paste the generated tags into your HTML <head> section"
faqs:
- q: "Does Google always use my meta description?"
a: "No — Google rewrites descriptions ~63% of the time. Write them anyway for
social shares and some SERPs."
- q: "What happens if my title is over 60 characters?"
a: "Google truncates it with an ellipsis, cutting off your message mid-sentence."
related_tools:
- og-tag-generator
- schema-markup-generator
- heading-analyzer
| Field | Minimum | Priority |
|---|---|---|
meta_title | Unique, ≤60 chars | 🔴 Critical |
meta_description | Unique, 120–160 chars | 🔴 Critical |
introduction | 80+ unique words | 🔴 Critical |
best_practices | 3–5 tool-specific items | 🟡 High |
how_to_steps | 3 real steps for THIS tool | 🟡 High |
faqs | 2 tool-specific Q&As | 🟡 High |
related_tools | 2–4 slug references | 🟢 Medium |
Rule: complete one tool fully before starting the next.
{Tool Name} | {Outcome Phrase}
Rules:
<h1> per page — only the tool name/titleh1 — Tool name (one per page)
h2 — Major sections: "How It Works", "Best Practices", "FAQs", "Related Tools"
h3 — Subsections: individual FAQ items, feature callouts, step headers
Never skip levels. No h1 → h3 without an h2.
# Audit heading hierarchy on a live page
curl -s "https://yourdomain.com/tools/meta-tag-generator" \
| grep -oE '<h[1-6][^>]*>.*?</h[1-6]>'
Accessibility failures lower Core Web Vitals scores — a direct ranking signal.
Every icon-only interactive element needs aria-label:
<button aria-label="Copy to clipboard"><svg>...</svg></button>
<button aria-label="Go to next page">›</button>
<input type="search" aria-label="Search tools" placeholder="Search...">
# Find icon-only buttons missing aria-label
B=$(grep -rn "<button" templates/ 2>/dev/null | grep -v "aria-label" | grep -v ">[A-Za-z]" | head -5)
[ -n "$B" ] && echo " ⚠ Icon buttons missing aria-label:" && echo "$B" || echo " ✓ Buttons have aria-labels"
Internal links between tools are how PageRank flows through your site. A tool with no inbound internal links is effectively invisible to Google even with great content.
Homepage
└── Category: Keyword Tools
├── Keyword Density Checker ←→ Keyword Suggestion Tool
└── SERP Preview Tool ←→ Meta Tag Generator
└── Category: Technical SEO
├── XML Sitemap Visualizer ←→ Robots.txt Creator
└── Robots.txt Creator ←→ Redirect Generator
related_tools from registry)# Orphan detection — tools with too few inbound references
for slug in $(cat data/slugs.txt 2>/dev/null); do
C=$(grep -rl "$slug" templates/ 2>/dev/null | wc -l | tr -d ' ')
[ "$C" -lt 2 ] && echo " ORPHAN RISK: $slug ($C refs)"
done
{% if tool.related_tools %}
<section>
<h2>Related Tools</h2>
{% for slug in tool.related_tools %}
{% set rel = get_tool(slug) %}
<a href="/tools/{{ slug }}">{{ rel.name }} — {{ rel.tagline }}</a>
{% endfor %}
</section>
{% endif %}
| ✅ Good | ❌ Bad | Problem |
|---|---|---|
/tools/meta-tag-generator | /tools/tool-1 | No keywords |
/tools/keyword-density-checker | /tools/free-online-keyword-density-checker-tool-free | Keyword stuffed |
/tools/broken-link-finder | /tools/brokenLinkFinder | camelCase |
Formula: {primary-keyword-phrase} — lowercase, hyphens, no stop words, no "free" / "online" / "tool" padding.
# Audit — list longest slugs (likely stuffed)
curl -s "https://yourdomain.com/sitemap.xml" \
| grep -oE '<loc>[^<]+' | sed 's/<loc>//' \
| grep "/tools/" \
| awk -F'/tools/' '{print length($2), $2}' | sort -n | tail -20
If renaming a slug, always 301 redirect old → new and update all internal links.
Tool sites rank poorly when they look authorless and dateless.
<p class="tool-byline">
Built by <a href="/about">Your Name</a>
<time datetime="{{ tool.updated_at }}"> · Updated {{ tool.updated_at | date }}</time>
</p>
<section class="trust-pillars">
<div>✅ <strong>100% Free</strong> — no account, no credit card</div>
<div>🔒 <strong>Privacy First</strong> — your data never leaves your browser</div>
<div>⚡ <strong>Instant Results</strong> — processed in under 1 second</div>
</section>
Create /about with: real name, credentials, why you built the tools, contact info.
Link to it from every tool page byline. This is the single highest-impact E-E-A-T fix
for solo-built tool sites.
When the content registry pattern is working for 10–20 tools, the next challenge is scaling it to 100+ without losing quality or introducing duplicates.
Never commit a partial batch. Before every commit touching tool content:
# Count tools with introduction content vs total tools
python3 -c "
import json
tools = json.load(open('data/tools.json'))
total = len(tools)
done = sum(1 for t in tools if t.get('introduction','').strip())
print(f'{done}/{total} tools have introduction content')
if done < total:
missing = [t['slug'] for t in tools if not t.get('introduction','').strip()]
print('Missing:', missing)
"
Only commit when the count is 100% complete. A partial batch (e.g. 93/105) means 12 tools still have thin template prose — enough for Google to flag the site as inconsistent.
Prioritise tools in this order:
# Confirm build compiles cleanly after batch content additions
npm run build # Next.js / Nuxt
python manage.py check # Django
rails assets:precompile # Rails
# Zero errors = safe to commit
Tool pages rank well for transactional keywords ("meta tag generator", "check broken links"). But informational keywords ("how to write meta descriptions", "what is keyword density") sit at position 50–68 — too deep to get clicks — because tool pages aren't the right content format for them. Blog posts are.
In Google Search Console → Search Results → filter by Position > 49 AND Position < 69. These are queries where you have enough authority to rank but the wrong page type is ranking.
Post title: {Informational keyword} — {Year} Guide
Target keyword: the exact query from GSC
Content length: 1,000–1,500 words
Internal links: link to 2–3 relevant tools from within the post body
Example mapping:
| GSC keyword (pos 50–68) | Blog post title | Tool to link |
|---|---|---|
| "how to write meta descriptions" | "How to Write Meta Descriptions That Get Clicks (2025)" | meta-tag-generator |
| "what is keyword density" | "Keyword Density: What It Is and How to Check It" | keyword-density-checker |
| "how to find broken links" | "How to Find and Fix Broken Links on Any Website" | broken-link-finder |
| "xml sitemap best practices" | "XML Sitemap Best Practices for 2025" | xml-sitemap-visualizer |
H1: {Target keyword} — the exact GSC query, naturally phrased
Intro (100 words): answer the question directly in the first paragraph
H2: What is {topic}?
H2: Why it matters for SEO
H2: How to {action} — step by step
H3: Step 1
H3: Step 2
H3: Step 3
H2: Common mistakes
H2: {Tool name} — try it free ← internal link to your tool
Conclusion: summarise + CTA to the tool
meta_title: include year where relevant ("2025") — improves CTR on informational queriesmeta_description: answer the question in one sentence + "Free tool included"canonical: must point to the exact blog URLdatePublished + dateModified in schema — critical for freshness signalsEvery blog post must contain at least 2 contextual inline links to relevant tools, not just a "Related Tools" sidebar. Inline links within body copy pass significantly more PageRank than sidebar links.
<!-- Good — inline contextual link -->
<p>Use our <a href="/tools/meta-tag-generator">meta tag generator</a> to preview
how your title and description appear in Google results before publishing.</p>
<!-- Weak — sidebar only, no body link -->
<aside>Related: Meta Tag Generator</aside>
A fix that compiles cleanly can still fail in production. After every push, verify the live site — not just the build.
seo:verify() {
local D="$1"; local F=0
for p in "/" "/tools/meta-tag-generator" "/blog" "/category" "/privacy" "/terms"; do
local C=$(curl -so /dev/null -w "%{http_code}" "$D$p")
echo "$C $p"; [ "$C" = "200" ] || ((F++))
done
local C=$(curl -so /dev/null -w "%{http_code}" "$D/tools/this-slug-does-not-exist-xyz")
echo "Soft 404 check: $C (expect 404)"; [ "$C" = "404" ] || { echo " ✗ Soft 404"; ((F++)); }
curl -s "$D/tools/meta-tag-generator" | grep -qi "canonical" && echo " ✓ Canonical present" || { echo " ✗ Canonical missing"; ((F++)); }
local C2=$(curl -so /dev/null -w "%{http_code}" "$D/favicon.ico")
echo "Favicon: $C2 (expect 200)"; [ "$C2" = "200" ] || { echo " ✗ Favicon missing"; ((F++)); }
local J=$(curl -s "$D/tools/meta-tag-generator" | grep -c "application/ld+json" || true)
[ "$J" -ge 1 ] && echo " ✓ Schema: $J blocks" || { echo " ✗ No schema found"; ((F++)); }
return $F
}
| Check | Expected |
|---|---|
| All key pages | 200 |
| Invalid tool slug | 404 |
<link rel="canonical"> present | Yes |
/favicon.ico | 200 |
application/ld+json blocks | ≥ 1 per tool page |
If any check fails — do not move on. Diagnose and fix before the next phase.
seo:validate() {
python3 validate_meta.py || return 1
python3 -c "
import json
from collections import Counter
tools = json.load(open('data/tools.json', 'r'))
titles = [t.get('meta_title', '') for t in tools]
dups = [t for t, c in Counter(titles).items() if c > 1 and t]
print(dups) if dups else print('All titles unique')
"
python3 -c "
import json
tools = json.load(open('data/tools.json', 'r'))
missing = [t['slug'] for t in tools if not t.get('introduction', '').strip()]
print(f'Missing intro ({len(missing)}):', missing[:10])
"
}
# Quick check — meta validation + live site verification
seo:quick() { seo:verify "$PROD_URL" && seo:validate; }
# Full check — quick + duplicate title check
seo:full() { seo:quick; }
| # | Issue | Severity | Phase |
|---|---|---|---|
| 1 | Duplicate / template prose across tool pages | 🔴 Critical | 2 |
| 2 | Missing or generic meta titles | 🔴 Critical | 1 |
| 3 | Missing or generic meta descriptions | 🔴 Critical | 1 |
| 4 | H1 shared across all tools | 🔴 Critical | 3 |
| 5 | Orphan tool pages (no inbound internal links) | 🟡 High | 5 |
| 6 | Missing related tool links | 🟡 High | 5 |
| 7 | Keyword-stuffed or keywordless URL slugs | 🟡 High | 6 |
| 8 | No author byline or last-updated date | 🟡 High | 7 |
| 9 | Heading hierarchy violations | 🟢 Medium | 3 |
| 10 | Missing aria-labels on icon buttons | 🟢 Medium | 4 |
| ✅ Do | ❌ Don't |
|---|---|
| Write unique meta title + description per tool | Use template prose shared across 80+ pages |
| Complete one tool's content fully before next | Batch-write partial entries across many tools |
| Link 2+ related tools from every tool page | Leave orphan tools with zero internal links |
Use {primary-keyword} in URL slug | Pad slugs with "free" / "online" / "tool" |
| Add author byline + date to every tool page | Show authorless, dateless content |
| Blog about informational keywords (pos 50–68) | Rely on tool pages to rank for "how to" queries |
| 301 redirect old slugs when renaming | Delete old slugs without redirects |
Duplicate content first, always. Identical template prose across 80+ pages is the root cause on almost every underperforming tool site. No other fix matters until this is done.
Complete one tool fully before the next. Never write partial entries across many tools. A half-written registry entry is worse than no entry — it signals thin content at scale.
Internal links are PageRank distribution. A tool with great content but zero inbound internal links is invisible to Google. Every tool needs at least 2 inbound links.
URL slugs are permanent. A clean slug outperforms a stuffed one from day one. Get them right before indexing — renaming later costs ranking momentum even with 301s.
E-E-A-T is not decoration. Authorless, dateless tool pages trigger quality rater guidelines as potential spam. A real name and a real date is the minimum baseline.
Validate before every commit. Catching a missing description before deploy is free. Fixing it after indexing costs weeks.
This skill is applicable to execute the workflow or actions described in the overview. Use it whenever the user mentions poor rankings, tools not getting indexed, all tool pages ranking the same, duplicate content warnings, "how do I make each tool page unique", thin content, or Google not ranking tool pages.
npx claudepluginhub sickn33/antigravity-awesome-skills --plugin antigravity-bundle-aas-mobile-app-builderOptimizes websites for search engines via technical SEO: robots.txt, meta robots, canonical URLs, XML sitemaps, URL structures, per Lighthouse audits and Google guidelines.
Audits web codebases for SEO and AI-answer visibility, checking page speed, crawlability, semantics, headings, then generates prioritized fix-it plans.
Plans and audits programmatic SEO for pages generated at scale from data sources, covering template engines, URL patterns, internal linking automation, thin content safeguards, and index bloat prevention.