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, pingPOST /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:
- BlogPosting — title, author, dates, publisher, image
- BreadcrumbList — Home > Blog > Article
- 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:
- Blog — publication name, description, publisher, canonical URL
- ItemList — the current page of
BlogPostingitems, in descending order by publish date, with 1-indexed positions - BreadcrumbList — Home > Blog
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 oldgetFreshnessLabel(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/authorwith 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