Skip to main content

Blog SEO & GEO Infrastructure

Strategy

Silvia’s blog targets two channels simultaneously:
  • SEO — ranking on Google for high-volume personal finance keywords
  • GEO — getting cited by AI engines (ChatGPT, Perplexity, Claude, Gemini) when users ask finance questions

robots.txt

We explicitly allow AI crawlers. Most sites block GPTBot, ClaudeBot, etc. We want them indexing our content because GEO is half our strategy. Allowed AI crawlers: GPTBot, ChatGPT-User, Google-Extended, PerplexityBot, ClaudeBot, Applebot-Extended Blocked paths: /api/, /auth/, /admin/, /studio/, /_next/

Sitemap

app/sitemap.ts includes:
  • Homepage (priority 1.0)
  • Blog index (priority 0.8, changes daily)
  • All published blog posts (priority 0.7, changes weekly)
  • Author page (priority 0.5)

IndexNow

When new articles are published, ping POST /api/indexnow with the new URLs. This instantly notifies Bing, Yandex, Seznam, and Naver. Google does not support IndexNow but re-crawls frequently via sitemap. Requires INDEXNOW_KEY env var. The key file must also exist at public/{key}.txt.

JSON-LD schemas

Article pages (/blog/[slug])

Every article emits three schemas via BlogPostJsonLd + BlogFaqJsonLd:
  1. BlogPosting — title, author, dates, publisher, image
  2. BreadcrumbList — Home > Blog > Article
  3. FAQPage — if the article has an FAQ section (detected from H2/H3 headings)

Index page (/blog)

The canonical index (page 1, no category, no query) emits three additional schemas via BlogIndexJsonLd:
  1. Blog — publication name, description, publisher, canonical URL
  2. ItemList — the current page of BlogPosting items, in descending order by publish date, with 1-indexed positions
  3. BreadcrumbList — Home > Blog
Filtered / paginated variants are noindex (via buildBlogIndexMetadata) and deliberately skip these schemas — rich results don’t fire on noindex pages, so duplicated JSON just adds crawl noise.

Internal linking

lib/blog/internal-links.ts generates 2-3 related article links per post. Priority: same category > recent posts. This builds topical clusters that Google rewards.

Content freshness

lib/blog/freshness.ts provides utilities for tracking article age:
  • isStale(updatedAt) — true if >90 days old
  • getFreshnessLabel(updatedAt) — “Updated today” / “Updated this week” / “Needs refresh”

OG images

Each article’s first visualization PNG is used as the Open Graph image for social sharing. Falls back to /images/og-image.png.

Financial disclaimer

BlogDisclaimer component renders on every article page. Required for finance content (YMYL).

E-E-A-T signals

  • Author page at /blog/author with methodology and content standards
  • “By Silvia” byline with the “AI Financial Advisor” role on Silvia-authored articles
  • Source attribution in every article (named sources with dates)
  • Fact-checking step documented on author page
  • Compliance scanning documented on author page