feat: SEO hardening — P0 meta fixes, auto JSON-LD, headers, RSS, recipes docs - #9
Merged
Conversation
- Emit full robots directives on indexable pages (max-snippet:-1, max-image-preview:large, max-video-preview:-1) so Google can show rich snippets. - Suppress canonical when noindex=true — two contradictory signals hurt rankings. - og:image now includes width/height/alt when provided; only emits if an ogImage is actually supplied (no more broken fallback to a non-existent /images/og-default.png). - twitter:card defaults to summary_large_image when an og:image is present (previously always summary unless user opted in). - Drop meta keywords emission — ignored by all major search engines. Prop retained with @deprecated JSDoc so existing page call sites don't break. - Declare <link rel="icon"> pointing at /favicon.svg — every site ships one but no head tag referenced it. - Add optional rssUrl/rssTitle props to advertise RSS feeds via <link rel="alternate" type="application/rss+xml"> (wired up in the Meridian RSS commit).
BaseLayout now derives siteUrl, logoUrl, and sameAs (from socialLinks) and passes them to SEOHead. SEOHead builds a linked @graph with: - WebSite node pointing at the site root with an inLanguage from locale - Organization node with logo (ImageObject) and sameAs (social URLs) - @id references so the two nodes are linked and additional nodes added via the structuredData prop can reference them User-supplied structuredData is merged into the same @graph (its @context is stripped to avoid duplication). Pages that need a fully custom graph can pass skipAutoJsonLd to opt out. Also exposes ogImageAlt, ogType, twitterHandle, noindex, rssUrl on BaseLayout so pages can wire them without importing SEOHead directly.
…ry-Search Ships the same _headers template across starter + all three demo sites. Cloudflare Pages and Netlify both honour this format; other CDNs ignore unknown directives so it's safe. - /_astro/* (including /_astro/fonts/*) gets a 1-year immutable cache — filenames are content-hashed so bumping the cache key isn't needed. - / gets No-Vary-Search to strip UTM + ad-click tracking params from the CDN cache key so marketing attribution doesn't fragment the cache.
Adds @astrojs/rss and /rss.xml endpoint generated from the insights content collection. Insights list and detail pages advertise the feed via <link rel="alternate" type="application/rss+xml"> (wired through BaseLayout → SEOHead rssUrl prop). Detail pages also now emit og:type=article so social unfurls and crawlers correctly classify them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Executes the P0 + P1 items from the SEO audit against
SEOHead.astro, the four sites, and Meridian's new insights collection. P2 items are documented indocs/seo-recipes.mdrather than baked in.What changed
packages/shared-ui/src/components/SEOHead.astroindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1) on indexable pages;noindex, nofollowon opted-out pagesnoindex=true— two contradictory signals hurt rankingsog:imagenow includeswidth,height, andalt; only emits when an image is actually supplied (no more broken fallback to a non-existent/images/og-default.png)twitter:carddefaults tosummary_large_imagewhen anog:imageis present@deprecatedso page call sites don't break<link rel="icon" href="/favicon.svg">— every site ships one but nothing referenced itWebSite+OrganizationJSON-LD@graphfrom props:siteName,Astro.site,logoSrc, andsocialLinks(derived assameAs). User-suppliedstructuredDatamerges into the same graph so@idrefs resolve. Opt out viaskipAutoJsonLd.BaseLayout:ogImageAlt,ogType,twitterHandle,noindex,rssUrl,skipAutoJsonLdsites/*/public/_headers/_astro/*(covers hashed JS, CSS, and fonts under/_astro/fonts/)No-Vary-Searchon/to strip UTM + ad-click params from the CDN cache keysites/meridian-advisory.com@astrojs/rss+/rss.xmlendpoint generated from the insights collectionrssUrl="/rss.xml"so<link rel="alternate" type="application/rss+xml">appears in headog:type=articledocs/seo-recipes.md(new)Documents the P2 items — per-page OG images (Satori + Sharp), git-based sitemap lastmod,
llms.txt, markdown alternates, IndexNow, FuzzyRedirect, view transitions, schema endpoints, NLWeb, build-time validation. Linked from README and CLAUDE.md.Why these specifically
All baked-in items ride on code the starter already has (a shared
SEOHeadcomponent,socialLinksalready in every site-config) and pay off on every site without user action. The P2 items need either extra deps (satori/sharp), per-site content structure decisions (which collections to emit asllms.txt), or an opinionated UX choice (view transitions) — so they belong in docs, not defaults.Test plan
bun run build --force— all 4 sites build clean from scratch@graph(WebSite + Organization with@idrefs andsameAsfrom social links)<link rel="alternate" type="application/rss+xml">/rss.xmlproduces valid RSS with 3 items, correct pubDates, author, categories_headerscopied into each site'sdist/at build/_astro/*immutable rule (network tab after deploy)Follow-ups (not in this PR)