Skip to content

feat(website): Emit per-page TechArticle JSON-LD and hreflang alternates#1589

Merged
yamadashy merged 3 commits into
mainfrom
seo/per-page-jsonld-hreflang
May 24, 2026
Merged

feat(website): Emit per-page TechArticle JSON-LD and hreflang alternates#1589
yamadashy merged 3 commits into
mainfrom
seo/per-page-jsonld-hreflang

Conversation

@yamadashy

Copy link
Copy Markdown
Owner

Summary

  • Resolve each page's locale from its source path (e.g. ja/guide/installation.md → locale ja, rest guide/installation).
  • Emit hreflang alternate links for all 15 supported locales plus x-default pointing at English, and an og:locale meta tag.
  • Emit a per-page TechArticle JSON-LD on documentation pages with inLanguage, mainEntityOfPage, and an isPartOf link to the existing global WebSite graph. The home page is intentionally excluded because the global SoftwareApplication entity already covers it.

Why

The previous setup carried a single global WebSite + SoftwareApplication JSON-LD graph and no hreflang annotation. Google, AI Overviews, and other LLM-backed search surfaces lacked the signal to route users to the matching localized page or to identify documentation articles as such.

Verification

  • node --run docs:build succeeds and the emitted HTML for dist/ja/guide/installation.html, dist/guide/installation.html, and dist/index.html contains the expected hreflang set, og:locale, and (for non-home pages) the TechArticle JSON-LD with the correct inLanguage.

Checklist

  • Run node --run lint (website/client)
  • Run node --run docs:build (website/client)

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7825ad96-0a12-430a-a80b-ed91e2e9e1b8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds locale-aware meta tag and structured data generation to a Vitepress website configuration file. It introduces locale configuration constants, builds helper functions to resolve page locale from request paths and construct localized URLs, and rewrites the head tag generator to emit canonical, hreflang, social metadata, and JSON-LD with locale-specific values.

Changes

Locale-aware page head generation

Layer / File(s) Summary
Locale configuration and mapping tables
website/client/.vitepress/config/configShard.ts
Introduces supportedLocales tuple defining the ordered list of locales, and creates lookup maps localeToBcp47 (for hreflang) and localeToOgLocale (for Open Graph og:locale values) covering all supported languages.
Locale resolution and URL building
website/client/.vitepress/config/configShard.ts
Adds stripPageSuffix to clean file extensions, resolvePageLocale to parse incoming Vitepress page paths and extract locale prefix and remainder (defaulting to en), and buildLocaleUrl to construct canonical/alternate URLs that omit locale prefix for English but include it for other languages.
Locale-aware head tag generation
website/client/.vitepress/config/configShard.ts
Rewrites createPageHead to resolve page locale from request path, set canonical and social metadata (og:locale, og:url, Twitter cards) using locale-aware URLs, emit <link rel="alternate" hreflang="..."> entries for all locales plus x-default fallback, and conditionally inject TechArticle JSON-LD with inLanguage and mainEntityOfPage fields derived from the resolved locale.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • yamadashy/repomix#1236: Both PRs modify website/client/.vitepress/config/configShard.ts to inject Schema.org JSON-LD into the page <head> (with the main PR making it locale-aware and the retrieved PR adding LLMO-related JSON-LD via the vitepress-plugin-llms wiring).
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: emitting TechArticle JSON-LD and hreflang alternates, which directly matches the primary objectives of the PR.
Description check ✅ Passed The description includes a comprehensive summary of changes, rationale (Why), verification details, and a completed checklist matching the template requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch seo/per-page-jsonld-hreflang

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Copy link
Copy Markdown
Contributor

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 enhances the site's SEO and metadata by implementing locale-aware head tags and structured data. It introduces a comprehensive list of supported locales with mappings to BCP-47 and Open Graph codes, and updates the head generation logic to include canonical URLs, locale-specific meta tags, and hreflang alternates. Additionally, it adds TechArticle JSON-LD structured data for documentation pages. The review feedback suggests exporting the supported locales list to ensure synchronization with the main VitePress configuration and extracting the author information into a shared constant to avoid duplication.

Comment thread website/client/.vitepress/config/configShard.ts Outdated
Comment thread website/client/.vitepress/config/configShard.ts Outdated
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 21, 2026

Copy link
Copy Markdown

Deploying repomix with  Cloudflare Pages  Cloudflare Pages

Latest commit: b363c23
Status: ✅  Deploy successful!
Preview URL: https://c5103381.repomix.pages.dev
Branch Preview URL: https://seo-per-page-jsonld-hreflang.repomix.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
website/client/.vitepress/config/configShard.ts (4)

41-97: 💤 Low value

Consider consolidating the three parallel locale tables.

supportedLocales, localeToBcp47, and localeToOgLocale must be kept in sync by hand whenever a locale is added or removed. A single source-of-truth map would prevent drift and let TypeScript guarantee completeness without the per-table Record<Locale, string> reminder.

♻️ Suggested consolidation
-const supportedLocales = [
-  'en',
-  'zh-cn',
-  ...
-] as const;
-
-type Locale = (typeof supportedLocales)[number];
-
-const localeToBcp47: Record<Locale, string> = { en: 'en', 'zh-cn': 'zh-CN', ... };
-const localeToOgLocale: Record<Locale, string> = { en: 'en_US', 'zh-cn': 'zh_CN', ... };
+const localeConfig = {
+  en:      { bcp47: 'en',    og: 'en_US' },
+  'zh-cn': { bcp47: 'zh-CN', og: 'zh_CN' },
+  'zh-tw': { bcp47: 'zh-TW', og: 'zh_TW' },
+  ja:      { bcp47: 'ja',    og: 'ja_JP' },
+  // ...
+} as const;
+
+type Locale = keyof typeof localeConfig;
+const supportedLocales = Object.keys(localeConfig) as Locale[];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/client/.vitepress/config/configShard.ts` around lines 41 - 97,
Replace the three parallel structures (supportedLocales, localeToBcp47,
localeToOgLocale) with a single as-const map object (e.g., localeDefinitions)
that maps each locale key to its bcp47 and ogLocale strings; derive
supportedLocales from Object.keys or from the map's keys and derive the Locale
type from the map's keys so TypeScript enforces completeness, then remove
localeToBcp47 and localeToOgLocale and replace their usages to read from
localeDefinitions[locale].bcp47 and .ogLocale (references: supportedLocales,
localeToBcp47, localeToOgLocale).

168-185: ⚡ Quick win

Strengthen graph linking between TechArticle.isPartOf and the global WebSite.

The global WebSite node (lines 194-199) has no @id, and the per-page isPartOf here re-declares an inline WebSite rather than referencing it. Search engines treat these as separate nodes. Giving the global WebSite a stable @id (e.g. ${siteUrl}#website``) and using isPartOf: { '@id`': '${siteUrl}`#website`' }` produces a single linked entity across pages — which is the PR's stated rationale for connecting article content to the product entity.

♻️ Suggested change
 // line 194 (global jsonLd)
 {
+  '`@id`': `${siteUrl}`#website``,
   '`@type`': 'WebSite',
   name: siteName,
   url: siteUrl,
   description: siteDescription,
 },
-      isPartOf: { '`@type`': 'WebSite', name: siteName, url: siteUrl },
+      isPartOf: { '`@id`': `${siteUrl}`#website`` },
       mainEntityOfPage: { '`@type`': 'WebPage', '`@id`': url },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/client/.vitepress/config/configShard.ts` around lines 168 - 185, The
article JSON-LD currently embeds an inline WebSite in articleJsonLd.isPartOf,
which prevents linking to the global WebSite node; update the global WebSite
JSON-LD to include a stable `@id` (e.g., `${siteUrl}`#website``) and change
articleJsonLd.isPartOf to reference that id via isPartOf: { '`@id`':
`${siteUrl}`#website`` } so all pages point to the same WebSite entity (refer to
articleJsonLd, isPartOf, siteUrl and the global WebSite JSON-LD block).

150-163: 💤 Low value

Optional: also emit og:locale:alternate for non-canonical locales.

You're emitting hreflang alternates for crawlers but not the OpenGraph equivalent. Adding one og:locale:alternate per non-current locale is a small win for social previews that honor it.

♻️ Suggested addition
   for (const alt of supportedLocales) {
     tags.push([
       'link',
       {
         rel: 'alternate',
         hreflang: localeToBcp47[alt],
         href: buildLocaleUrl(alt, rest),
       },
     ]);
+    if (alt !== locale) {
+      tags.push(['meta', { property: 'og:locale:alternate', content: localeToOgLocale[alt] }]);
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/client/.vitepress/config/configShard.ts` around lines 150 - 163, Add
an OpenGraph locale-alternate meta for each non-canonical locale: inside the
loop over supportedLocales (the for (const alt of supportedLocales) block) push
an additional tag like ['meta', { property: 'og:locale:alternate', content:
localeToBcp47[alt] }] for every alt that is not the current locale (compare alt
!== currentLocale or your equivalent variable), or if you prefer, after the loop
iterate supportedLocales and push that meta for each non-current alt; ensure you
use localeToBcp47[alt] for the content and add the tags to the existing tags
array the same way buildLocaleUrl and hreflang are added.

137-146: ⚡ Quick win

Set og:type to article on non-home documentation pages to match the TechArticle JSON-LD

createPageHead emits TechArticle JSON-LD for !isHome, but the global head hardcodes og:type to website; add ['meta', { property: 'og:type', content: isHome ? 'website' : 'article' }] to the per-page tags array.

VitePress merges head entries and doesn’t dedupe by property/name, so remove the global og:type tag when moving it to createPageHead to avoid duplicate OG tags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/client/.vitepress/config/configShard.ts` around lines 137 - 146,
Update the per-page head tags in createPageHead to set og:type based on isHome
by adding ['meta', { property: 'og:type', content: isHome ? 'website' :
'article' }] to the tags array (the function createPageHead and its local tags
variable are the targets), and remove the global hardcoded og:type meta from the
top-level head so VitePress doesn't emit duplicate Open Graph tags; ensure the
new tag is only added in createPageHead for non-home pages to match the
TechArticle JSON-LD.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@website/client/.vitepress/config/configShard.ts`:
- Around line 41-97: Replace the three parallel structures (supportedLocales,
localeToBcp47, localeToOgLocale) with a single as-const map object (e.g.,
localeDefinitions) that maps each locale key to its bcp47 and ogLocale strings;
derive supportedLocales from Object.keys or from the map's keys and derive the
Locale type from the map's keys so TypeScript enforces completeness, then remove
localeToBcp47 and localeToOgLocale and replace their usages to read from
localeDefinitions[locale].bcp47 and .ogLocale (references: supportedLocales,
localeToBcp47, localeToOgLocale).
- Around line 168-185: The article JSON-LD currently embeds an inline WebSite in
articleJsonLd.isPartOf, which prevents linking to the global WebSite node;
update the global WebSite JSON-LD to include a stable `@id` (e.g.,
`${siteUrl}`#website``) and change articleJsonLd.isPartOf to reference that id via
isPartOf: { '`@id`': `${siteUrl}`#website`` } so all pages point to the same WebSite
entity (refer to articleJsonLd, isPartOf, siteUrl and the global WebSite JSON-LD
block).
- Around line 150-163: Add an OpenGraph locale-alternate meta for each
non-canonical locale: inside the loop over supportedLocales (the for (const alt
of supportedLocales) block) push an additional tag like ['meta', { property:
'og:locale:alternate', content: localeToBcp47[alt] }] for every alt that is not
the current locale (compare alt !== currentLocale or your equivalent variable),
or if you prefer, after the loop iterate supportedLocales and push that meta for
each non-current alt; ensure you use localeToBcp47[alt] for the content and add
the tags to the existing tags array the same way buildLocaleUrl and hreflang are
added.
- Around line 137-146: Update the per-page head tags in createPageHead to set
og:type based on isHome by adding ['meta', { property: 'og:type', content:
isHome ? 'website' : 'article' }] to the tags array (the function createPageHead
and its local tags variable are the targets), and remove the global hardcoded
og:type meta from the top-level head so VitePress doesn't emit duplicate Open
Graph tags; ensure the new tag is only added in createPageHead for non-home
pages to match the TechArticle JSON-LD.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a6d69936-cbca-436c-888a-10833bb2a90b

📥 Commits

Reviewing files that changed from the base of the PR and between eb945d9 and e75f2cc.

📒 Files selected for processing (1)
  • website/client/.vitepress/config/configShard.ts

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

⚡ Performance Benchmark

Latest commit:b363c23 fix(website): Address PR review feedback for SEO config
Status:✅ Benchmark complete!
Ubuntu:0.41s (±0.04s) → 0.42s (±0.05s) · +0.00s (+0.5%)
macOS:0.53s (±0.07s) → 0.57s (±0.11s) · +0.05s (+9.1%)
Windows:0.87s (±0.01s) → 0.88s (±0.01s) · +0.01s (+0.8%)
Details
  • Packing the repomix repository with node bin/repomix.cjs
  • Warmup: 2 runs (discarded), interleaved execution
  • Measurement: 20 runs / 30 on macOS (median ± IQR)
  • Workflow run
History

de72078 [autofix.ci] apply automated fixes

Ubuntu:0.74s (±0.03s) → 0.73s (±0.02s) · -0.01s (-1.8%)
macOS:0.57s (±0.13s) → 0.54s (±0.10s) · -0.03s (-6.0%)
Windows:0.86s (±0.01s) → 0.86s (±0.01s) · +0.00s (+0.3%)

yamadashy and others added 3 commits May 22, 2026 00:29
Documentation pages now publish a per-page `TechArticle` JSON-LD with
`inLanguage`, `mainEntityOfPage`, and an `isPartOf` link to the
existing global `WebSite` graph. The home page intentionally skips the
article schema since it is already represented by the
`SoftwareApplication` entity.

Every page also emits `hreflang` alternate links (BCP-47) for all
supported locales plus `x-default` pointing at English, and an
`og:locale` meta. This gives Google and AI Overviews enough
language/locale signal to route users to the matching translation
instead of falling back to English.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Consolidate `supportedLocales`, `localeToBcp47`, and `localeToOgLocale`
  into a single `localeConfig` map and export it along with the
  `Locale` type so the locale list lives in one place and can be reused
  by the main VitePress config later.
- Extract the duplicated author block into a shared `siteAuthor`
  constant referenced by both the global SoftwareApplication graph and
  the per-page TechArticle.
- Give the global `WebSite` node a stable `@id` and reference it from
  `TechArticle.isPartOf` so search engines see a single linked entity
  across pages instead of inlined duplicates.
- Emit per-page `og:type` (`article` for docs, `website` for the home
  page) and drop the global `og:type` so the OpenGraph type matches the
  TechArticle schema.
- Add `og:locale:alternate` for every non-current locale alongside the
  existing `hreflang` alternates so social previews can also route to
  the matching localized page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yamadashy yamadashy force-pushed the seo/per-page-jsonld-hreflang branch from de72078 to b363c23 Compare May 21, 2026 15:30
@yamadashy yamadashy merged commit f5773c8 into main May 24, 2026
27 checks passed
@yamadashy yamadashy deleted the seo/per-page-jsonld-hreflang branch May 24, 2026 06:28
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.

1 participant