astro-seo

Audits and improves SEO for Astro sites. Use when the user asks to audit, set up, or improve SEO on an Astro site, or mentions head metadata, structured data, JSON-LD, sitemaps, IndexNow, Open Graph images, schema endpoints, NLWeb, hreflang, or search engine indexing in an Astro project. Produces dr

By jdevalk · 400 installs

npx skills add jdevalk/skills --skill astro-seo

Source repository · Upstream listing

Astro SEO Audits and improves the SEO setup of an Astro site against the full stack described in [Astro SEO: the definitive guide](https://joost.blog/astro seo complete guide/). The skill covers nine areas — technical foundation, structured data, content, site structure, performance, sitemaps and indexing, agent discovery, redirects, and analytics — and produces drop in code for anything missing or weak. The opinionated spine of this skill is [ @jdevalk/astro seo graph ](https://github.com/jdevalk/seo graph). Most of the fixes route through it. If the project doesn't use it yet, installing it is the first recommendation. Code recipes live in AGENTS.md — read it when you need to implement a specific fix. This file has the workflow and audit checklist. Workflow 1. Detect the project — confirm this is an Astro site and understand its shape. 2. Audit — score nine categories and produce actionable findings. 3. Improve — generate or modify files to close the gaps. Recipes are in AGENTS.md . 4. Metadata pass — invoke metadata check on every short string the skill generated (titles, descriptions, schema description fields, FAQ answers, frontmatter excerpts). 5. Verify — run the build, check validations pass, remind the user about non file tasks (Search Console, Bing Webmaster Tools, IndexNow key verification). Phase 0: Detect the project Confirm the basics before auditing: astro.config.mjs / astro.config.ts exists. package.json has astro as a dependency. site: is set in astro.config — canonicals, sitemaps, and OG image URLs all derive from this. If it's missing, empty, or http://localhost , flag it as a blocking issue before anything else. This is the single most common misconfiguration. Content collections in src/content/ (or legacy src/pages/ markdown). Deployment target — read package.json , vercel.json , netlify.toml , wrangler.toml , or public/ headers to determine the host. This drives redirect and header syntax in Phase 2. Is @jdevalk/astro seo graph already installed? If yes, record the version and which features are wired (grep for <Seo , seoGraph( , createSchemaEndpoint , createSchemaMap , FuzzyRedirect , createIndexNowKeyRoute , createMarkdownEndpoint , createApiCatalog , gitLastmod ). Check the installed version against the latest on npm with npm view @jdevalk/astro seo graph version . If the project is behind, recommend an upgrade in Phase 2 before auditing feature gaps — the package ships new defaults and fixes regularly, and an outdated version is a plausible cause for any audit finding. Phase 2 branches on this. Is the site multilingual? Check for i18n in astro.config or multiple locale directories under src/content/ . If yes, hreflang matters; if no, skip it. Ask only what you can't detect. Don't ask the user what the site is about — read astro.config.mjs and the homepage content. Phase 1: Audit Score each category out of 10. For each, give 2–4 specific findings that quote the actual code or config. Within each category, checks are tiered: Must — ship blockers. A failure here causes visible SEO regression. Should — standard practice. Skipping costs reach. Nice — forward looking or situational. Useful but not baseline for every site. Skip Nice checks for small personal blogs unless the user asks for the full treatment. 1. <Seo component and head metadata (/10) Must — single component for all head metadata (not scattered across layouts). Must — site: in astro.config is set to the production origin. Must — canonical URLs derived from site config with tracking params stripped. Must — canonical omitted when noindex is true (per Google's recommendation). Must — fallback chain for missing SEO fields: seo.title → title → siteName ; seo.description → excerpt → first paragraph . Pages with blank titles or descriptions are the most common symptom of a broken fallback. Should — robots meta includes max snippet: 1 , max image preview:large , max video preview: 1 . Should — Twitter tags suppressed when they duplicate Open Graph (Twitter falls back automatically). Should — hreflang alternates present on multilingual sites. Skip if monolingual. Nice — uses @jdevalk/astro seo graph 's <Seo component rather than hand rolled. (Hand rolled that covers everything above is fine; this skill nudges toward the package because it handles the fallback chain and robots rules by default.) 2. Structured data / JSON LD graph (/10) Single flat Article object, or a linked @graph with multiple entities? Entities wired with @id references? WebSite , Blog / WebPage , Person / Organization , BlogPosting / Article , BreadcrumbList , ImageObject all present where relevant? Trust signals: publishingPrinciples , copyrightHolder , copyrightYear , knowsAbout , SearchAction ? Validates in [Rich Results Test](https://search.google.com/test/rich results) and [ClassySchema](https://classyschema.org/Visualisation)? 3. Content collections and SEO schema (/10) Content collections defined with Zod schemas? seoSchema from @jdevalk/astro seo graph enforcing title (5–120) and description (15–160) lengths? Required fields ( publishDate , title , excerpt ) enforced at build time? Markdown stripped articleBody exposed in schema endpoints (up to 10K chars)? 4. Open Graph images (/10) Every page has an OG image, or many missing? 1200×675 (Google Discover minimum 1200px wide, 16:9 ratio)? Generated at build time via satori + sharp, or manual? JPEG (social platforms don't reliably support WebP/AVIF)? Route derives OG URL from the slug automatically? Every <img in rendered HTML has an alt attribute (or alt="" / role="presentation" for decorative images)? validateImageAlt on seoGraph() catches this at build time in ≥ 1.1.0. 5. Sitemaps and indexing (/10) Must — @astrojs/sitemap installed, sitemap index reachable. Must — robots.txt references the sitemap index. Must — RSS feed exists ( @astrojs/rss ), advertised via <link rel="alternate" type="application/rss+xml" , contains full post content (not truncated excerpts). Should — split per collection via chunks option ( sitemap posts 0.xml , etc.) — much easier to debug indexing in GSC. Should — lastmod populated from git commit timestamps, not frontmatter dates or CI file timestamps. @jdevalk/astro seo graph ≥ 1.4.0 exports gitLastmod(filePath, { excludeCommits, depth }) for this — it returns the committer date of the most recent non excluded commit that touched the file, or null if git is unavailable. Wire it into the sitemap serialize callback. If the codebase has a hand rolled execSync('git log ...') helper, replace it with the package export — the export handles bulk commit exclusion (imports, reformats) which a naïve 1 lookup can't. Should — IndexNow integrated and submitting on each build, with key verification route at /[key].txt . ≥ 1.0.1 excludes /404 from submissions by default. Gate submission on the production host (e.g. process.env.CF PAGES === '1' && CF PAGES BRANCH === 'main' , VERCEL ENV === 'production' , CONTEXT === 'production' ). Unconditional submission pings the endpoint on every local npm run build and preview deploy with URLs the production host hasn't served yet, which gets the key marked invalid (403) and forces rotation. 6. Agent discovery (/10) Should — schema endpoints ( /schema/ .json ) exposing corpus wide JSON LD. Should — schema map ( /schemamap.xml ) listing all endpoints, with Schemamap: directive in robots.txt . Should — [ llms.txt ](https://llmstxt.org) at the site root listing pages (title + description) for LLM consumers. @jdevalk/astro seo graph ≥ 0.9.0 generates this via the llmsTxt integration option. Should — markdown alternate URLs ( /blog/post.md next to /blog/post/ ) serving clean markdown with YAML frontmatter for AI agents to consume without HTML parsing. @jdevalk/astro seo graph ≥ 1.2.0 ships createMarkdownEndpoint for the route and a markdownAlternate: true integration option that emits <link rel="alternate" type="text/markdown" on every page. ≥ 1.3.0 adds post build verification: the integration walks the build output and strips any link whose .md target isn't on disk (with a warn per occurrence) — so a misconfigured endpoint surfaces as a build warning instead of a shipped 404. Pair with a Cloudflare Transform Rule (URL rewrite via wildcard replace , no Vary: Accept header — CF strips custom Vary values) for content negotiation on Accept: text/markdown without needing SSR. Should — API catalog at /.well known/api catalog per [RFC 9727](https://www.rfc editor.org/rfc/rfc9727). @jdevalk/astro seo graph ≥ 1.4.0 ships createApiCatalog , which auto types schema endpoints to their https://schema.org/<Type URLs and absolutizes paths against siteUrl . List the schema endpoints, schemamap, and any site specific APIs ( /ask , /feed.xml , etc.). The RFC is finalized so the format is stable; adoption is early but the cost is one route file. Should — Content Signals directive in robots.txt (e.g. Content Signal: ai train=yes, search=yes, ai input=yes ). Declares preferences for AI training, search grounding, and AI input use independently of crawl access. The spec is an [IETF draft](https://datatracker.ietf.org/doc/draft romm aipref contentsignals/) and adoption is early, but it's one line in a file every site already has. Should — Link header on / pointing to discovery files (sitemap, llms.txt, api catalog, schemamap, and any MCP / A2A cards the site publishes). Agents reading response headers find them without parsing HTML. On Cloudflare Pages / Netlify this is public/ headers ; on Vercel it's vercel.json . Nice — MCP server card at /.well known/mcp/server card.json ([SEP 1649](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127)) and / or A2A agent card at /.well known/agent card.json ([A2A protocol](https://a2a protocol.org/)). Only relevant when the site actually exposes an MCP server or A2A agent. Both are static JSON files declaring the endpoint, capabilities, and skills. Skip otherwise. Nice — <link rel="nlweb" pointing to a conversational endpoint. NLWeb is early; the tag is one line and worth having, but it's not a scoring blocker in 2026. Nice — ARD ([Agentic Resource Discovery](https://agenticresourcediscovery.org/)) catalog at /.well known/ai catalog.json , listing what the domain offers (MCP server, A2A agent, OKF bundle, site specific APIs). v0.9 draft, so optional — but it's the discovery layer the MCP / A2A cards and OKF bundle pay off through. Two gotchas: the base spec ([ Agent Card/ai catalog ](https://github.com/Agent Card/ai catalog)) names the media type field mediaType while the ARD layer ([ ards project/ard spec ](https://github.com/ards project/ard spec)) names it type — emit both keys with the same value so the entry validates under either reading (both specs require consumers to ignore unknown keys). ARD also adds an optional representativeQueries array of sample prompts per entry. Nice — OKF ([Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge catalog)) bundle: a tree of typed Markdown concept files (one per page, paths mirroring canonical URLs) packaged as a single .tar.gz , with index files in between. Serving and discovery are explicit non goals of OKF — that's what the ARD catalog entry is for. v0.9 draft, optional. There's no registered media type yet , so the interim string is application/okf bundle+gzip (tracked in [knowledge catalog 111](https://github.com/GoogleCloudPlatform/knowledge catalog/issues/111) and [ard spec 27](https://github.com/ards project/ard spec/issues/27)); mark it interim, it may change. Best generated as one more o