Skip to content

Conversation

@jamiehenson
Copy link
Member

@jamiehenson jamiehenson commented Nov 28, 2025

Human preamble

For this ticket. Crawlers are not able to get a good look at language variants of docs pages. Since rendering anchor links in the LanguageSelector is not viable (disrupts SPA behaviour, not within reach in react-select or Radix), this PR renders a set of links in the page when multiple languages are available, and also adds some JSON-LD structured data to the head.

The lang param is also considered in the canonical URL when present since it fundamentally changes the page context so should be indexed separately.

To test: look at a docs pages with languages on (i.e. this) and then look at the JSON-LD in the header, and the hidden links in the foot.

Summary

  • Add JSON-LD structured data with TechArticle schema for programming language variants
  • Add hidden crawlable anchor tags for language selector options
  • Ensures all language variants are discoverable by crawlers and search engines

Changes

1. JSON-LD Structured Data (Head.tsx, MDXWrapper.tsx)

Implemented proper Schema.org structured data to signal programming language variants to search engines:

TechArticle Schema: Represents technical documentation pages

  • Uses hasPart property to link to multiple code examples
  • Includes headline, description, and canonical URL

SoftwareSourceCode Schema: Represents each programming language variant

  • programmingLanguage property indicates the specific language (JavaScript, Python, Flutter, etc.)
  • url property links to the page with ?lang= query parameter

Example output:

{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Real-time Channels",
  "description": "Learn how to use real-time channels...",
  "url": "https://ably.com/docs/channels",
  "hasPart": [
    {
      "@type": "SoftwareSourceCode",
      "programmingLanguage": "JavaScript",
      "url": "https://ably.com/docs/channels?lang=javascript"
    },
    {
      "@type": "SoftwareSourceCode",
      "programmingLanguage": "Python",
      "url": "https://ably.com/docs/channels?lang=python"
    }
  ]
}

This follows Schema.org best practices and is recommended by Google for structured data.

2. Hidden Crawlable Links (HiddenLanguageLinks.tsx, Layout.tsx)

Created a component that renders visually hidden <a> tags for each language option:

  • Uses sr-only class and aria-hidden="true" to hide from users
  • Remains crawlable by basic tools (curl, wget, AI agents)
  • Placed at bottom of Layout component
  • Only renders when multiple language options are available

3. Language Deduplication (layout-context.tsx)

Added Set deduplication to prevent duplicate language entries in the language list.

Why This Matters

Problem

Currently, basic crawlers and search engines cannot discover the language variants available in our language selector because:

  1. The Radix Select component uses dynamic JavaScript
  2. No HTML links exist in the page source
  3. No structured metadata signals the programming language variants

Solution

  1. JSON-LD structured data properly tells search engines "this page has code examples in X, Y, Z languages" using standardized Schema.org vocabulary
  2. Hidden anchor tags ensure basic crawlers (curl, AI agents) can discover and follow links to language variants
  3. Together, these improve SEO and make our docs more discoverable

Note on rel="alternate"

Initially considered using rel="alternate" but this is semantically incorrect - it's meant for language translations (en/fr/es) or alternate formats (PDF/RSS), not programming language code variants. JSON-LD is the proper approach.

Test Plan

  • Verify JSON-LD appears in page <head> (view source, search for application/ld+json)
  • Verify structured data validates with Google Rich Results Test
  • Verify hidden links appear in page source (view source, search for sr-only)
  • Verify links point to correct URLs with ?lang= query params
  • Verify language selector UI/UX remains unchanged
  • Test with a basic crawler (curl) to ensure links are discoverable

🤖 Generated with Claude Code

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Important

Review skipped

Auto 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.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch web-4830-alternate-languages

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

@jamiehenson jamiehenson changed the title fix: add crawlable language selector links for SEO [WEB-4830] fix: add crawlable language selector links for SEO Nov 28, 2025
@jamiehenson jamiehenson force-pushed the web-4830-alternate-languages branch 2 times, most recently from 898492e to 730d593 Compare November 28, 2025 15:00
@jamiehenson jamiehenson changed the title [WEB-4830] fix: add crawlable language selector links for SEO [WEB-4830] Add crawlable language selector links for SEO Nov 28, 2025
@jamiehenson jamiehenson force-pushed the web-4830-alternate-languages branch from 730d593 to 5c2081a Compare November 28, 2025 15:42
Add alternateLanguageLinks prop to Head component to support proper SEO signaling for language variants. This renders <link rel="alternate" hrefLang="..."> tags for each available language option, following SEO best practices for multi-language content.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@jamiehenson jamiehenson force-pushed the web-4830-alternate-languages branch 2 times, most recently from f6884bb to f96be2f Compare November 28, 2025 16:25
@jamiehenson jamiehenson temporarily deployed to ably-docs-web-4830-alte-odgjwb November 28, 2025 16:30 Inactive
jamiehenson and others added 3 commits November 28, 2025 16:35
Add HiddenLanguageLinks component that renders hidden anchor tags for each language variant, making them discoverable by crawlers and search engines. Update Layout to include this component at the bottom of the page. Update MDXWrapper to generate and pass alternate language links to the Head component for proper SEO signaling.

This ensures that all language variants of documentation pages are discoverable by basic crawlers (curl, wget) and properly indexed by search engines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add comprehensive tests for:
- Head component JSON-LD structured data rendering
- HiddenLanguageLinks component crawlable links generation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@jamiehenson jamiehenson force-pushed the web-4830-alternate-languages branch from a5ac740 to 70d2e7f Compare November 28, 2025 16:35
languages = activePageData.page.languages; // Use language overrides from the nav data first if possible
} else if (pageContext?.languages) {
languages = pageContext.languages.map(stripSdkType) as LanguageKey[]; // Use pageContext languages if available, this is generated for MDX pages
languages = Array.from(new Set(pageContext.languages.map(stripSdkType))) as LanguageKey[]; // Use pageContext languages if available, this is generated for MDX pages
Copy link
Member Author

Choose a reason for hiding this comment

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

Solves a separate problem where langs could be duplicated when there's a full bevy of realtime and rest langs

@jamiehenson jamiehenson marked this pull request as ready for review November 28, 2025 16:37
@jamiehenson jamiehenson changed the title [WEB-4830] Add crawlable language selector links for SEO [WEB-4830] Add crawlable language selector links and structured data for SEO Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants