Skip to content

Add SEO/AEO optimizations for blog listing and detail pages#3

Merged
linfangw merged 4 commits intomainfrom
feat/seo-aeo-optimization
Apr 7, 2026
Merged

Add SEO/AEO optimizations for blog listing and detail pages#3
linfangw merged 4 commits intomainfrom
feat/seo-aeo-optimization

Conversation

@linfangw
Copy link
Copy Markdown
Contributor

@linfangw linfangw commented Apr 7, 2026

Summary

  • JSON-LD structured data: BlogPosting schema on blog detail pages (headline, datePublished, author, keywords, wordCount, publisher), BreadcrumbList on detail pages, CollectionPage with ItemList on blog listing
  • Open Graph enhancements: og:type correctly set to "article" on detail pages, added og:locale, og:site_name, article:published_time, article:modified_time, article:author, article:tag, og:image:width/height/alt
  • hreflang multi-language: <link rel="alternate" hreflang="en/zh/x-default"> on all pages for search engine locale association
  • Heading hierarchy fix: Blog listing page section labels changed from <span> to semantic <h2> to fix h1->h3 skip
  • AEO (Answer Engine Optimization): Visible summary paragraph at top of blog posts, visible tags at bottom, meta name="author" in head
  • Twitter Card fix: property="twitter:*" changed to name="twitter:*" per spec

New component

  • src/components/JsonLd.astro — Reusable JSON-LD generator supporting BlogPosting, BreadcrumbList, CollectionPage schemas

Test plan

  • pnpm build passes (0 errors, 9 pages)
  • Validate JSON-LD via Google Rich Results Test
  • Verify og:type=article on blog detail pages
  • Verify hreflang tags present on all pages
  • Verify visible summary and tags on blog post pages
  • Verify heading hierarchy: h1 -> h2 -> h3 on listing page

🤖 Generated with Claude Code

Structured data (JSON-LD):
- BlogPosting schema on blog detail pages (headline, description,
  datePublished, dateModified, author, keywords, articleSection,
  wordCount, publisher, mainEntityOfPage)
- BreadcrumbList schema on blog and doc detail pages
- CollectionPage schema on blog listing page with ItemList

Open Graph enhancements:
- og:type now correctly set to "article" on blog/doc detail pages
  (was hardcoded "website" for all pages)
- Added og:locale (en_US / zh_CN), og:site_name
- Added article:published_time, article:modified_time, article:author,
  article:tag for blog posts
- Added og:image:width, og:image:height, og:image:alt

Twitter Card fix:
- Changed property="twitter:*" to name="twitter:*" per spec

hreflang multi-language:
- Added <link rel="alternate" hreflang="en/zh/x-default"> on all pages
  for search engine locale association

Heading hierarchy fix:
- Blog listing page: replaced <span class="eyebrow"> section labels
  with semantic <h2> elements (styled as eyebrows) to fix h1->h3 skip

AEO (Answer Engine Optimization):
- Visible summary paragraph at top of blog posts (renders description
  as a lead paragraph with aqua left-border styling)
- Visible tags rendered as badge links at bottom of blog posts
- meta name="author" added to head

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive SEO and AEO (AI Engine Optimization) enhancements, including a new JsonLd component for structured data, expanded metadata in BaseHead (Open Graph, Twitter Cards, and hreflang), and semantic improvements to blog and documentation layouts. Feedback focuses on ensuring consistency in hreflang values for Chinese locales, centralizing site URL logic to prevent duplication and potential build errors, correcting wordCount calculations for non-English content, and conditionally rendering post summaries to avoid visual artifacts when descriptions are missing.

Comment thread src/components/BaseHead.astro Outdated
Comment thread src/components/JsonLd.astro Outdated
Comment thread src/layouts/BlogPost.astro Outdated
Comment thread src/layouts/BlogPost.astro Outdated
…uard

Address all 4 Gemini review comments:

1. BaseHead: hreflang="zh" changed to "zh-CN" for consistency with
   html lang attribute and JSON-LD inLanguage values.

2. Centralize site URL: Added SITE_URL constant to consts.ts, replaced
   hardcoded 'https://qveris.ai' fallback in JsonLd.astro, BlogPost.astro,
   DocPage.astro, and blog listing page.

3. BlogPost: wordCount calculation now language-aware
   (cn: readingTime * 300, en: readingTime * 200) matching the
   reading-time utility's per-language word rates.

4. BlogPost: post-summary paragraph now conditionally rendered
   only when description is non-empty to prevent empty styled blocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linfangw linfangw requested a review from vxwork April 7, 2026 10:10
@linfangw
Copy link
Copy Markdown
Contributor Author

linfangw commented Apr 7, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements significant SEO and AEO (Answer Engine Optimization) improvements, including a new JsonLd component for structured data and enhanced metadata in BaseHead. Blog posts now feature visible summaries and tags, while section headings have been updated for better semantic structure. Feedback highlights the need for safer handling of optional dates in the JsonLd component to prevent runtime errors, more accurate schema mapping for authors, and a more scalable approach to generating hreflang tags.

Comment thread src/components/JsonLd.astro Outdated
Comment thread src/components/JsonLd.astro Outdated
Comment thread src/components/JsonLd.astro Outdated
Comment thread src/components/BaseHead.astro Outdated
Address all 4 Gemini review comments:

1. (HIGH) JsonLd: pubDate.toISOString() now uses conditional spread
   to prevent runtime crash when pubDate is undefined.

2. (MEDIUM) JsonLd: pubDate changed to optional (pubDate?: Date) in
   BlogPostingData interface for safer use with doc pages.

3. (MEDIUM) JsonLd: author field now uses Person type when a specific
   author name is provided, falls back to Organization for QVeris.

4. (MEDIUM) BaseHead: hreflang links now dynamically generated from
   the locales array via getHtmlLang(), instead of hardcoded en/zh-CN.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linfangw
Copy link
Copy Markdown
Contributor Author

linfangw commented Apr 7, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive SEO and AEO (AI Engine Optimization) enhancements, including a new JsonLd component for structured data, dynamic hreflang link generation, and expanded metadata for blog and documentation layouts. The review feedback focuses on improving the robustness of URL generation by providing fallbacks for Astro.site to prevent build-time errors, securing JSON-LD script injection against potential termination issues, and refining the accuracy of structured data types and word count metrics.

Comment thread src/components/BaseHead.astro
Comment thread src/components/BaseHead.astro Outdated
Comment thread src/components/BaseHead.astro Outdated
Comment thread src/components/JsonLd.astro Outdated
Comment thread src/layouts/BlogPost.astro Outdated
Comment thread src/layouts/BlogPost.astro Outdated
Comment thread src/layouts/DocPage.astro Outdated
Comment thread src/layouts/DocPage.astro Outdated
Comment thread src/pages/blog/[lang]/index.astro Outdated
… actual wordCount

Address all 9 Gemini review comments:

1-5. (MEDIUM) Consistent SITE_URL fallback: Added SITE_URL to consts.ts
   import in BaseHead.astro. All Astro.site usages now use a site
   fallback variable. Reordered canonicalUrl construction in BlogPost
   and DocPage to use the fallback. Listing page pageUrl also fixed.

6. (MEDIUM) JSON-LD XSS protection: escape < as \u003c in
   JSON.stringify output to prevent </script> injection.

7. (MEDIUM) Actual wordCount: reading-time utility now returns both
   minutes and wordCount via getReadingStats(). BlogPost passes the
   real count instead of a readingTime * N heuristic.

8. (MEDIUM) DocPage uses TechArticle schema instead of BlogPosting
   for better semantic accuracy on documentation pages.

9. (MEDIUM) JsonLd component now supports TechArticle as a schema type
   via the refactored buildArticle() function.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linfangw linfangw merged commit b5e8ff8 into main Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants