website-seo-geo
Skill9 starsChanged 30 days agoMITView on GitHub
The on-page SEO + GEO contract for a new site: exact metadata limits (title 50–60 chars, meta description 140–160 with a 120 floor), og:title/og:description == title/desc, twitter inherits OG, clean canonical == og:url, required JSON-LD schema, and an llms.txt index of the site. Use when building or reviewing a page's head / metadata (step 6 of the new-website pipeline), or when a title/description is too long or tags are out of sync. Enforced by the seo.spec.ts check in the new-website test suite. Delegates depth to ai-seo (GEO) and schema-markup. Trigger phrases: "SEO metadata", "title too long", "meta description length", "og tags", "twitter card", "canonical", "is my SEO set up", "llms.txt", "GEO", "head tags", "structured data on this page".
- Installs packages
1 other project uses a version of this.
Adopted by
Most recently taken up by
- karero/croftweaverword for word, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 1
- karero/croftweaver★ 9100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository41 other files it gives its agents.
- skills/new-website/templates/AGENTS.mdAGENTS.md
- skills/new-website/templates/CLAUDE.mdCLAUDE.md
- ai-seoSkill
- astro-i18n-setupSkill
- business-listings-setupSkill
- copywritingSkill
- customer-researchSkill
- double-knuthSkill
- facts-checkSkill
- imageSkill
- independent-reviewSkill
- internal-link-auditSkill
- keystatic-setupSkill
- new-websiteSkill
- og-imagesSkill
- outgoing-link-auditSkill
- schema-markupSkill
- search-console-insightsSkill
- search-console-setupSkill
- seo-auditSkill
- seo-repositionSkill
- site-architectureSkill
- website-brand-kitSkill
- website-contact-formSkill
- website-content-guideSkill
- website-design-systemSkill
- website-hero-carouselSkill
- website-launch-checkSkill
- website-motionSkill
- website-new-pageSkill
- website-permissionsSkill
- website-positioning-checkSkill
- website-positioningSkill
- website-qaSkill
- website-reviewSkill
- website-scorecardSkill
- website-storySkill
- website-taglineSkill
- website-team-setupSkill
- website-testimonialsSkill
- wordpress-inspectorSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 51
If Barrierefreiheitsprüfung (24 chars alone) doesn't leave room for anything else, reach for a shorter near-synonym or split brand/category …
- Very specific: line 97
Every page: WebSite + Organization (once, site-wide) and a WebPage node.
- Very specific: line 98
Add page-type schema via schema-markup: BreadcrumbList on nested pages, FAQPage on FAQs, Article/TechArticle with author @id + dates on …
- Distinctive: line 153
After fixing a live PDF, re-submit its URL in Bing/Google so it recrawls.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Website SEO & GEO
The metadata contract every page must satisfy. This skill owns the exact
limits and consistency rules; it hands depth off to the existing global skills:
ai-seo (AI answer-engine / GEO strategy), schema-markup (rich JSON-LD),
seo-audit (technical SEO / crawl / Core Web Vitals — see also website-qa).
The limit table (enforced)
| Tag | Ideal | Hard cap | Why |
<title> | 50–60 chars | ~60 | Google truncates ~580–600px; >60 risks a cut, >70 almost always cut |
<meta description> | 140–160 chars | ~160 | Truncates ~920px; >160 cut; <120 wastes space / keyword room |
og:title / og:description | = <title> / = description | — | Test fails on any divergence |
twitter:title / twitter:description | inherit OG (same strings) | — | twitter:card = summary_large_image |
The test counts characters (em-dash, en-dash, curly apostrophe each = 1 char but
render wider — stay clear of the cap). <meta name="description"> is separate
from og:description; one does not replace the other — ship both, in sync.
German (and other compounding languages) eat the budget faster
The limits above are pixel-render caps, not word counts — they don't change per
language, and the numbers in the table stay exactly as specified. But German
compound nouns (Suchmaschinenoptimierung, Barrierefreiheit,
Barrierefreiheitsprüfung) pack far more meaning per character into a single
unbroken word than English does, so the same 50–60 / 140–160 char cap holds
noticeably less content once translated. Write for it deliberately:
- Front-load the key term. German syntax tends to push the important noun toward the end of a compound or clause; if you translate an English title 1:1 the keyword can land past where truncation cuts. Put the primary keyword/term first, then qualify it, rather than burying it in a trailing compound.
- When one natural compound blows the budget, don't force it — restructure.
If
Barrierefreiheitsprüfung(24 chars alone) doesn't leave room for anything else, reach for a shorter near-synonym or split brand/category from descriptor with a colon or dash:Website-Builder: barrierefrei & SEO-optimiertreads better and fits, where one giant compound noun forces an awkward truncation or abbreviation. - Budget for ~10–15% fewer effective words of meaning than an English title/description of the same character count. Plan the German copy's content around that from the start — don't write the English version, translate it, and then trim words until the character count fits; that's how you end up with a mangled abbreviation instead of a deliberate, readable title.
- Target the LOWER half of the title range (~50–55 chars) for German. Google truncates by pixel width (~580px), not characters — German capitalizes every noun, and capitals plus umlauts render wide, so a passing 60-char German title can still be cut in the SERP. The 60-char cap is a ceiling, not a safe harbor.
This is a writing tactic, not a test change: seo.spec.ts enforces the same
character counts regardless of language, and it should — the fix here is how you
write within that budget, not the budget itself.
Consistency rules
- One pair of inputs
(title, description)drives<title>, OG, Twitter, canonical and the WebPage schema — wired once in the new-website skill'stemplates/astro/src/layouts/Base.astro, so pages only passtitle/descriptionand the tags can never drift. - Canonical is clean (no
.html), absolute, and equalsog:urland the page'ssitemap.xml<loc>. - No trailing slash (convention): URLs are
/about, not/about/(home stays/). The starter setstrailingSlash: 'never'+build.format: 'file'; write internal links without a trailing slash.navigation.spec.tsflags any/about/link (404/redirect hop) andseo.spec.tspinscanonical == SITE.url + path, so the whole site stays consistent. - Exactly one
<h1>per page (its text MAY differ from the keyword-tuned title). - ASCII slugs only: transliterate German umlauts/ß to digraphs (
/ueber-uns, not/über-uns) — machine-serialized URLs (canonical, sitemap) percent-encode non-ASCII while hand-written strings (PAGES, llms.txt, authored links) stay raw, a mismatch factory (site-architecture has the full rule). - og:locale derives from
<html lang>(de → de_DE; regioned tags → base+region; one sharedogLocaleFor()insrc/config.ts, emitted by Base.astro, asserted byseo.spec.tswhenever a value derives — mapped bases and all regioned tags) — a German page silently carryingog:locale en_USis exactly the drift this row exists to stop.
Schema (JSON-LD) — minimum per page
- Every page:
WebSite+Organization(once, site-wide) and aWebPagenode. - Add page-type schema via
schema-markup:BreadcrumbListon nested pages,FAQPageon FAQs,Article/TechArticlewith author@id+ dates on posts,Service/Productas relevant. Author/Organization@ids andsameAsfeed EEAT (seewebsite-content-guide); keepsameAsURLs that actually resolve. FAQPagedescribes the Q&A to machines, not a Google rich result. Google stopped showing FAQ rich results on 7 May 2026 and removed their documentation on 15 June 2026 (Google, removal); no site gets one now. Don't promise one.- Datetimes in JSON-LD carry an explicit timezone.
VideoObject.uploadDate,Event.startDate/endDate,Offer.validFrom,Articledates etc. must be full ISO-8601 with an offset (2026-03-11T00:00:00+00:00, not a bare2026-03-11). Google's video parser rejects a date-onlyuploadDateas "missing a timezone" / "invalid datetime value" — a GSC error that only surfaces weeks after launch. - Validate with Google Rich Results Test / schema.org validator before launch.
GEO (generative engine optimization)
- Ship
/llms.txt(template in the starters): a curated, accurate index of key pages + facts, for AI tools and people that read it. Ship it because it is cheap: no crawler page of OpenAI, Perplexity or Anthropic says it reads the file, and Google says Google Search doesn't use it but keeping one for other services is fine (Google). Write it in the site's primary content language (German site → German descriptions; multilingual site → each entry's description in that page's language) — same precedent as the privacy page: an answer engine quoting your English blurb to a German searcher is the wrong-language failure again. Keep it in sync with the site — route coverage is enforced both ways in the new-website starter suite:tests/llms-coverage.spec.tsfails if anyPAGESroute is missing from it, or if a same-site entry goes stale (page removed/renamed). Add the page's- [Title](URL): descriptionline in the same commit as the page; deliberately-hidden pages (paid-ad landers) go in itsLLMS_EXEMPTwith a reason. On a site that predates the guard, copy the spec in from the starter before relying on it. - For deeper GEO/AEO (being cited by ChatGPT/Perplexity/AI Overviews, answer
formatting, entity coverage), run
ai-seo— do not duplicate it here.
PDFs you host (the doc-title IS the search-result title)
A PDF has no <title> — Google and Bing print its embedded doc-title as
the search-result title. Authoring tools ship a placeholder
("PowerPoint-Präsentation", "Microsoft Word - report.docx") or a working-filename
slug, which Bing flags as "page title too short" (seen in production on a real
site built with this kit, 2026-06-19). So any PDF you put in public/ needs a
descriptive doc-title set before it ships:
- Title convention: the same human title the page uses for that document —
e.g.
Talk Title — Speaker | Brand. ≥15 chars, never the authoring placeholder. - Set it in BOTH places crawlers read — the Info dict
/Titleand the XMPdc:title. Fixing only one leaves the stale title visible. Use the helper:
(needspython3 scripts/set_pdf_title.py public/<file>.pdf "Descriptive Title | Brand" --author "Author"pip install pypdf; local authoring tool only — the test below is dep-free.) - Enforced by
seo.spec.ts: the PDF guard scanspublic/**/*.pdfand fails the build on a placeholder, a title under 15 chars, or no doc-title at all. No-op for sites that host no PDFs. - After fixing a live PDF, re-submit its URL in Bing/Google so it recrawls.
Done means
npm test green — specifically seo.spec.ts: every title/description within
limits, og/twitter/canonical in sync, JSON-LD parses, a sitemap exists, and every
hosted PDF carries a descriptive doc-title (not an authoring placeholder).
Judgment pass (what the tests cannot check)
seo.spec.ts proves the metadata is well-formed; it cannot tell you it is good.
Once it is green, do a one-page judgment read — this is a model call (Rule 5: use the
model only where code can't answer):
- Title matches search intent, not just ≤60 chars — would a searcher click it over its SERP neighbours?
- Description earns the click — answers the query, not keyword-stuffed.
- Schema is semantically right — correct
@typeand real relationships, not merely valid JSON (a valid-but-wrong@typestill passes the test). llms.txtis accurate vs the live site — facts and wording current, no drift (route coverage is the mechanical half, pinned bytests/llms-coverage.spec.ts; this read is about the wording half).- EEAT signals resolve —
sameAs/ author URLs actually load and corroborate.
Intent and semantics only. Structural correctness is the test's job; cross-file
route/URL/config/SEO consistency is the website-review (Double-Knuth) skill's job.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

