SEO & AEO for Eleventy Docs
Audit, fix, and validate SEO and AEO (AI Engine Optimization) for Eleventy documentation sites. This skill routes to sub-commands for analysis, remediation, and CI validation.
Sub-commands
| Command | Description | |---------|-------------| | seo-aeo audit | Audit the site for SEO/AEO issues and produce a report | | seo-aeo fix | Analyze gaps and apply fixes to source files | | seo-aeo validate | Run the validation script against built output |
If no sub-command is provided, display the table above and ask which sub-command to run.
Phase 0: Prerequisites
<critical_sequence>
Before executing any sub-command, verify the Eleventy site structure exists.
Required: Eleventy config
if [[ ! -f "eleventy.config.js" ]] && [[ ! -f ".eleventy.js" ]]; then
echo "No Eleventy config found. This skill works with Eleventy documentation sites."
# Stop execution
fi
Required for validate: Built output
The validate sub-command requires a built _site/ directory. If missing:
No_site/directory found. Runnpx @11ty/eleventyfirst, then re-run validate.
</critical_sequence>
Sub-command: audit
Run a comprehensive SEO/AEO audit using the seo-aeo-analyst agent.
Steps
- Launch the seo-aeo-analyst agent via the Task tool:
`` Task seo-aeo-analyst: "Audit this Eleventy documentation site for SEO and AEO issues. Read the site configuration, templates, and data files. Produce a structured report with critical issues, warnings, and passed checks. Do NOT make any changes." ``
- Present the agent's report to the user
- Offer next steps: "Run
seo-aeo fixto apply recommended fixes, orseo-aeo validateto check the built output."
Sub-command: fix
Analyze gaps and apply targeted fixes to source files.
Steps
- Launch the seo-aeo-analyst agent via the Task tool with fix instructions:
`` Task seo-aeo-analyst: "Audit this Eleventy documentation site for SEO and AEO issues. For each issue found, apply a fix to the source files. Read each file before editing. After all fixes, build the site with npx @11ty/eleventy and run bash ${CLAUDE_PLUGIN_ROOT:-plugins/soleur}/skills/seo-aeo/scripts/validate-seo.sh _site and bash ${CLAUDE_PLUGIN_ROOT:-plugins/soleur}/skills/seo-aeo/scripts/validate-csp.sh _site to verify. Report what was changed and whether validation passed." ``
- Present the agent's report showing changes made
- If validation failed, show the failing checks and offer to re-run fix
Sub-command: validate
Run the standalone validation script against built output.
Steps
- Verify
_site/exists. If not, build first:
``bash npx @11ty/eleventy ``
- Run the validation scripts:
``bash bash ${CLAUDE_PLUGIN_ROOT:-plugins/soleur}/skills/seo-aeo/scripts/validate-seo.sh _site bash ${CLAUDE_PLUGIN_ROOT:-plugins/soleur}/skills/seo-aeo/scripts/validate-csp.sh _site ``
- Report results:
- Exit 0 on both: All SEO and CSP checks passed
- Exit 1: Show failing checks and recommend running
seo-aeo fix
Validation scripts:
- [validate-seo.sh](./scripts/validate-seo.sh) -- SEO/AEO element checks
- [validate-csp.sh](./scripts/validate-csp.sh) -- Content-Security-Policy hash integrity checks
Known limitations:
- validate-seo.sh only checks named AI bot entries -- wildcard
User-agent: *blocks are not detected. A site blocking all bots via wildcard will pass validation. - validate-csp.sh requires Python 3 for reliable multi-line HTML parsing and SHA-256 hash computation.
Sharp edges
- Bare-relative href sweep target list. When dropping
<base href="/">(or any change that defeats relative-href resolution), the sweep file list ispages/**/*.njk,_includes/**/*.njk,index.njk,404.njk,llms.txt.njk,sitemap.njk,page-redirects.njk, andblog/**/.md. Siblings ofindex.njkat the docs root are easy to miss because they don't live underpages/. Verify post-sweep:grep -rnE 'href="[a-z][a-z-]/' plugins/soleur/docs/ | grep -vE 'https?:|mailto:|//'must be empty. Inline@font-faceurl('fonts/...')and preloadhref="css/..."resolve against the document URL too — convert those to absolute in the same edit. See PR #2973. - Bulk URL change in
_data/site.jsonrequires Nunjucks consumer sweep. Root-slashing nav URLs breaks any template predicate that concatenates'/' + item.url(silent —aria-currentsimply stops activating). Grep_includes/**/*.njkfor every place the field is consumed (item.url,link.url) and verify each comparison still computes against the new shape. Same class as the sweep rules in AGENTS.md (cq-raf-batching-sweep-test-helperset al). See PR #2973. - Eleventy custom date filters must guard falsy input.
new Date(undefined).toISOString()throwsRangeError. Any custom Eleventy filter that delegates to the Date constructor must return null on falsy input AND the calling template must wrap its JSON-LD line in{% if page.date %}so a null result does not emit invalid schema.org. Example:dateToRfc3339ineleventy.config.js. validate-seo.shper-page substitutions need|| true. Inside the per-page loop underset -euo pipefail,count=$(grep -oE 'X' "$f" | wc -l)aborts the script on zero matches because pipefail propagates grep's exit 1. Usecount=$(grep -cE 'X' "$f" || true)—grep -creturns the count (0 on no match) and the rescue keeps the script alive.- A GSC "Page with redirect" cluster is usually Google's historical memory, not a live sitemap leak. The affected-URL list is dominated by URLs Google learned before a canonicalization (www→apex,
.html→clean,?ref=query forms) that now 3xx correctly — it self-heals server-side as Google re-crawls; there is no repo defect to chase. The only actionable repo condition is a redirecting URL present in the builtsitemap.xml. Verify THAT specific condition against a freshnpx @11ty/eleventybuild (grep -oE '<loc>[^<]+</loc>' _site/sitemap.xml | grep -E '(\.html$|/pages/)'must be empty); do not treat the GSC URL list as a bug list. The redirect-stub gate invalidate-seo.shenforces it at deploy/CI time. Seeknowledge-base/project/learnings/2026-06-01-gsc-page-with-redirect-is-historical-memory-verify-against-build.md. - A GSC "Duplicate, Google chose different canonical than user" report on a
www(or other non-canonical-variant) URL is the same benign class — Google followed the variant's 301 to the apex, read the apex page's correct self-canonical, and consolidated the variant onto it (working correctly). Verify via livecurl: variant →301→ apex, apex →200, apex canonical self-referential. The fix is operator VALIDATE FIX in GSC + wait ~2-4 weeks, NOT a code change — and never "correct"site.urltowww(the inverse of the #4573 apex flip; would emit a redirecting canonical site-wide). The per-page canonical-host gate invalidate-seo.shasserts each page's canonical host matches the sitemap host (catches a template regression; a uniformsite.urlflip stays covered bysentry_uptime_monitor.soleur_www). Seeknowledge-base/project/learnings/2026-06-12-gsc-duplicate-canonical-on-www-variant-is-benign-consolidation.md.