fix(marketing): SEO/AEO/GEO audit fixes across the landing pages - #309
Conversation
Three parallel audits (technical SEO, answer-engine optimization, generative-
engine/LLM-citation optimization) against every non-blog marketing page.
Implemented the verified, high-confidence findings; skipped subjective P2/P3
phrasing suggestions to control risk.
Real bug caught while implementing a GEO suggestion (not from the audits
themselves): "five browser-local tools" is stated in three places (index.astro
FAQ, tools/index.astro meta description, llms.txt) but the tools array has
seven entries. All three now say seven, and llms.txt's tool count is now
${tools.length}-derived so this can't silently drift again.
Also caught while implementing: my own first pass at the llms.txt Vibe
Security 50 fix imported VIBE_SECURITY_CONTROLS via the "@lyrashield/security"
package alias. vibe-security-50.astro has an explicit comment explaining why
that specific import breaks the Cloudflare Worker bundle (pulls in undici via
the package's SSRF/fetch re-exports) and previously broke the production
deploy (workspace:* protocol isn't understood by the plain-npm install step
inside wrangler-action). Fixed to use the same relative import
vibe-security-50.astro uses before it ever shipped.
GEO (llms.txt.ts, the highest-leverage single file for LLM citation)
- "Last updated" used new Date() at request time, meaning it read as "today"
on every request regardless of whether content changed — the opposite of
the freshness principle astro.config.mjs already documents for the sitemap.
Now a hand-bumped content-date constant.
- Vibe Security 50's "43 controls... 7 controls" was hardcoded prose,
duplicating a fact vibe-security-50.astro derives from a registry with a
build-time drift guard. Now imports the same registry (via the same safe
relative path) so the two pages cannot disagree.
- Strengthened the top-line "what is LyraShield" definition to name all four
product surfaces (SaaS, CLI, GitHub Action, MCP server) as one product,
added a standalone "release assurance" definition, added an explicit
pricing-today statement (free to join, no card, no announced plan yet), and
added a citable open-source/license statement (verified against this
repo's own README, which states MIT — not asserted from outside knowledge).
- Added /support, /security-reporting, /privacy to the public URL list (all
three were recently un-drafted and published but never added here).
- Enumerated the five... er, seven free tools by name instead of only linking
them, so an LLM can answer "what free tools does LyraShield offer" from
this file alone.
AEO
- ai-safety.astro had no FAQPage schema despite being exactly the "what is
X / does Y do Z" content profile every sibling trust page competes on;
every sibling page (methodology, vibe-security-50, evidence-vault, scan,
sample-report) has one. Added a 4-item FAQ restating existing "what this is
not" content as Q&A, plus a standalone lead sentence restructuring the
strongest boundary claim (guard scope vs. content moderation) out of a
checklist fragment into one self-contained sentence.
- agents.astro's HowTo schema described four steps with no matching visible
<ol> anywhere on the page — Google's HowTo/list-snippet feature wants the
markup and schema to correspond. Added a real numbered list mirroring the
four HowToStep entries 1:1, linking out to the #clients and #safety
sections for steps 3 and 4.
- evidence-vault.astro's genuinely sequential 4-step "how evidence becomes
assurance" process (Submit/Review/Version/Snapshot) had no HowTo schema at
all despite being a clean fit; added it, matching the working pattern from
sample-report.astro.
- Un-buried two homepage FAQ answers (the roadmap question, the
"replacement for scanners" question) so the direct answer leads instead of
arriving after two sentences of context-setting.
- Fixed one internally-jargon FAQ question phrasing on scan.astro ("inspect"
-> "look for", matching how a person actually searches).
Technical SEO
- Trimmed three titles that exceeded ~65 chars after verifying the actual
rendered length myself (about.astro, vibe-security-50.astro,
docs/integrations/agent-rules.astro) — a few other pages the initial audit
flagged turned out fine on direct measurement and were left alone.
- Expanded two meta descriptions that were far under the 120-160 target
(support.astro, security-reporting.astro) and trimmed two that were over
(ai-safety.astro from 182 to 143 chars, index.astro from 163 to 122).
- Added dateModified to vibe-security-50.astro and evidence-vault.astro's
WebPage JSON-LD, which had none — both pages carry the site's most
load-bearing numeric claims (the 50/43/7 split) that other pages and
llms.txt cite, so their complete absence of a freshness signal was
disproportionately costly. Added matching visible "Last reviewed" text.
- Removed a leftover internal-file reference (docs/claims-readiness.md,
a path that only exists inside this repo, not on the public site) from
methodology.astro, ai-safety.astro, and llms.txt.ts's public copy.
GEO citation-quality
- Fixed dangling-pronoun sentences that would mangle if an LLM quoted them
in isolation: methodology.astro ("those facts" -> the actual noun phrase)
and vibe-security-50.astro ("This is not... it is never shown" -> named
subject in both clauses).
- Linked scan.astro's WebApplication entity back to index.astro's
SoftwareApplication entity via isPartOf, so a crawler building a knowledge
graph from JSON-LD sees one product with a free entry point rather than
two unrelated applications.
Verification
No node_modules, npm returns 403 — astro check/eslint/prettier/vitest could
not run locally. Instead: re-derived and checked every assertion in
seo.test.ts, agent-onboarding.test.ts, waitlist.test.ts, premium-preview.test.ts,
and lite-scan.test.ts against the final state of every edited file by hand (39
checks, all passing) rather than trusting the edits were safe. One test
assertion in seo.test.ts was updated deliberately: it pinned the old hardcoded
"43 controls..." string, which the registry-driven fix correctly replaced —
updated it to assert the registry wiring and the safe import path instead of a
fixed string, so the test would actually fail if the file ever regressed to
the broken package-alias import. CI is the execution gate.
Co-Authored-By: Claude <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 20 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (14)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
npm returns 403 in this sandbox so prettier could not be run locally to catch this before CI. One expect(...).toContain(...) call in the new seo.test.ts assertion was split across three lines but fit under printWidth 100 collapsed to one — Prettier collapses single short-string arguments like this rather than leaving them wrapped. Swept the rest of the file's multi-line toContain calls by hand; the other four all exceed 100 chars collapsed and correctly stay split. Co-Authored-By: Claude <noreply@anthropic.com>
DRAFT — three parallel audits (technical SEO, answer-engine optimization, generative-engine/LLM-citation optimization) against every non-blog marketing page, following up on the earlier UI/UX + content audit (#308).
Two real bugs caught while implementing, neither from the original audits
/toolsmeta description,llms.txt). All three corrected, andllms.txt's count is now${tools.length}-derived so this can't silently drift again the way it apparently already had.llms.txt's hardcoded "43/7" controls split, I importedVIBE_SECURITY_CONTROLSvia the@lyrashield/securitypackage alias —vibe-security-50.astrohas an explicit comment explaining that exact import pulls inundicivia the package's SSRF/fetch re-exports, breaks the Cloudflare Worker bundle, and previously broke the production deploy outright (workspace:*isn't understood by the plain-npm install step insidewrangler-action). Caught it against that comment before it ever shipped and switched to the same relative import the sibling page already uses safely.GEO —
llms.txt.ts, the highest-leverage single file for LLM citationnew Date()at request time — read as "today" on every single request regardless of whether anything changed, the exact anti-patternastro.config.mjsalready documents avoiding for the sitemap. Now a hand-bumped content-date constant.vibe-security-50.astroderives from a registry with its own build-time drift guard. Now imports the same registry, same safe path — the two pages structurally cannot disagree again./support,/security-reporting,/privacyto the public URL list (all three were published in feat(marketing): hero collage, motion warm-up, and landing-page audit fixes #308 but never added here).AEO
ai-safety.astrohad no FAQPage schema despite being exactly the "what is X / does Y do Z" profile every sibling trust page competes on. Added a 4-item FAQ restating existing "what this is not" content as Q&A, plus restructured the strongest boundary claim out of a checklist fragment into one self-contained, quotable sentence.agents.astro's HowTo schema described 4 steps with no matching visible<ol>anywhere on the page. Added a real numbered list mirroring the schema 1:1.evidence-vault.astro's genuinely sequential 4-step process had no HowTo schema at all despite being a clean fit (mirrors the working pattern already insample-report.astro). Added it.Technical SEO
ai-safety.astro182→143,index.astro163→122).dateModifiedtovibe-security-50.astroandevidence-vault.astro, which had none — both pages carry the site's most load-bearing numeric claims that other pages andllms.txtcite, so a total absence of freshness signal was disproportionately costly. Added matching visible "Last reviewed" text.docs/claims-readiness.md, a path that only exists inside this repo) frommethodology.astro,ai-safety.astro, andllms.txt.ts's public copy.GEO citation quality
methodology.astro,vibe-security-50.astro).scan.astro'sWebApplicationentity back toindex.astro'sSoftwareApplicationviaisPartOf, so a knowledge-graph crawler sees one product with a free entry point, not two unrelated applications.Verification
No
node_modules, npm returns 403 —astro check/eslint/prettier/vitestcould not run locally. Instead: re-derived and checked every assertion inseo.test.ts,agent-onboarding.test.ts,waitlist.test.ts,premium-preview.test.ts, andlite-scan.test.tsagainst the final state of every edited file by hand — 39 checks, all passing — rather than trusting the edits were safe. One assertion inseo.test.tswas deliberately updated: it pinned the old hardcoded "43 controls..." string, which the registry-driven fix correctly replaced; updated it to assert the registry wiring and the safe import path instead, so the test now actually fails if the file ever regresses to the broken package-alias import. CI is the gate.Consciously not done: several P2/P3 subjective copy-rewrite suggestions from the audits (Speakable schema, some minor phrasing tweaks, compare-page pricing recency inline anchors) — skipped to control scope and risk on a branch that's already touching a lot of surface area.