Skip to content

SEO Optimization

The optimizer package enhances Starlight’s basic SEO with advanced meta tags, Open Graph protocol, Twitter Cards, structured data (Schema.org), and social sharing optimization.

While Starlight provides basic SEO (title, description, canonical), production documentation sites need more sophisticated optimization for:

  • Social media sharing (Open Graph, Twitter Cards)
  • Rich search results (structured data)
  • Better indexing (additional meta tags)
  • Cross-platform compatibility

Open Graph is a protocol that controls how URLs are displayed when shared on social media (Facebook, LinkedIn, WhatsApp, Discord, Slack, etc.).

Without Open Graph:

https://docs.example.com/guide/installation
Installation Guide - My Documentation
docs.example.com

With Open Graph:

┌─────────────────────────────────────┐
│ [Large preview image] │
│ │
│ Installation Guide │
│ Step-by-step installation │
│ instructions for getting started. │
│ │
│ 🔗 docs.example.com │
└─────────────────────────────────────┘

The optimizer automatically generates Open Graph tags for every page:

<!-- Basic Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://docs.example.com/guide/installation" />
<meta property="og:title" content="Installation Guide" />
<meta property="og:description" content="Step-by-step installation instructions" />
<meta property="og:image" content="https://docs.example.com/og-image.png" />
<meta property="og:site_name" content="My Documentation" />
<meta property="og:locale" content="en_US" />
<!-- Optional tags -->
<meta property="og:updated_time" content="2025-02-17T10:00:00Z" />
<meta property="article:published_time" content="2025-01-15T10:00:00Z" />
<meta property="article:modified_time" content="2025-02-17T10:00:00Z" />
<meta property="article:author" content="Your Name" />
<meta property="article:section" content="Guides" />
<meta property="article:tag" content="installation, setup, getting-started" />

Configure Open Graph globally:

astro.config.mjs
starlightOptimizer({
seo: {
// Default OG image for all pages
ogImage: '/og-default.png',
// Site name
siteName: 'My Documentation',
// Default type
ogType: 'website', // or 'article' for docs
// Author information
author: {
name: 'Your Name',
url: 'https://example.com',
},
// Locale
locale: 'en_US',
// Alternative locales
alternateLocales: ['es_ES', 'fr_FR', 'de_DE'],
},
}),

Override OG tags per page with frontmatter:

---
title: Installation Guide
description: Step-by-step installation instructions
seo:
ogImage: /og-installation.png
ogType: article
author: Jane Doe
publishedTime: 2025-01-15
modifiedTime: 2025-02-17
section: Guides
tags: [installation, setup, getting-started]
---
# Installation Guide
Your content here...

Recommended specifications:

PlatformRecommended SizeAspect Ratio
Facebook1200 x 630 px1.91:1
LinkedIn1200 x 627 px1.91:1
Twitter1200 x 675 px16:9
Discord1200 x 630 px1.91:1
Slack1200 x 630 px1.91:1
WhatsApp1200 x 630 px1.91:1

Optimal universal size: 1200 x 630 pixels (1.91:1)

Image format:

  • ✅ PNG (best quality)
  • ✅ JPEG (smaller file size)
  • ⚠️ WebP (not universally supported)
  • ❌ SVG (not supported for OG)

File size:

  • Target: < 300 KB
  • Maximum: < 1 MB (Facebook limit: 8 MB)

Design tips:

┌─────────────────────────────────────┐
│ [Logo] [Icon/Visual] │ <- Top 20%: Safe from cropping
│ │
│ Main Title │ <- Center: Most important
│ (Large, Bold) │
│ │
│ Subtitle or tagline │ <- Support text
│ │
│ example.com [CTA] │ <- Bottom: URL, call-to-action
└─────────────────────────────────────┘

:::tip Dynamic OG Images Consider using services like Vercel OG Image Generation or Cloudinary to generate OG images automatically from page content. :::

Test how your pages appear when shared:

ToolURLPurpose
Facebook Debuggerdevelopers.facebook.com/tools/debugPreview Facebook sharing
LinkedIn Inspectorlinkedin.com/post-inspectorPreview LinkedIn sharing
Twitter Card Validatorcards-dev.twitter.com/validatorPreview Twitter cards
OpenGraph.xyzopengraph.xyzUniversal preview

Facebook Debugger workflow:

  1. Paste your page URL
  2. Click “Debug”
  3. View preview
  4. Click “Scrape Again” to refresh cache (after changes)

Twitter Cards enhance how links appear on Twitter/X with rich media previews.

Without Twitter Card:

docs.example.com
Installation Guide

With Twitter Card:

┌─────────────────────────────────────┐
│ [Large preview image] │
│ │
│ Installation Guide │
│ Step-by-step installation... │
│ 🔗 docs.example.com │
└─────────────────────────────────────┘
Card TypeUse CaseDisplay
summaryDefault, small imageText-focused with small thumbnail
summary_large_imageRecommended for docsLarge preview image (recommended)
appMobile app promotionApp install card
playerVideo/audio contentEmbedded media player
<!-- Twitter Card meta tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@yourhandle" />
<meta name="twitter:creator" content="@authorhandle" />
<meta name="twitter:title" content="Installation Guide" />
<meta name="twitter:description" content="Step-by-step installation instructions" />
<meta name="twitter:image" content="https://docs.example.com/og-image.png" />
<meta name="twitter:image:alt" content="Installation Guide preview" />
starlightOptimizer({
seo: {
twitter: {
card: 'summary_large_image', // Recommended
site: '@yourdocs', // Your site's Twitter handle
creator: '@yourhandle', // Author's Twitter handle
imageAlt: 'Documentation preview', // Default alt text
},
},
}),
---
title: Installation Guide
seo:
twitterCard: summary_large_image
twitterImage: /twitter-install.png
twitterImageAlt: Installation steps diagram
twitterCreator: '@janedoe'
---

:::note Image Specifications Twitter Card images:

  • Minimum: 300 x 157 px
  • Maximum: 4096 x 4096 px
  • Recommended: 1200 x 675 px (16:9)
  • File size: < 5 MB :::

Structured data helps search engines understand your content and can result in rich snippets in search results.

Without structured data:

Installation Guide - My Documentation
https://docs.example.com/guide/installation
Step-by-step installation instructions for getting started...

With structured data (rich snippet):

Installation Guide - My Documentation ⭐⭐⭐⭐⭐
https://docs.example.com/guide/installation › guides
📘 Technical Article · Updated Feb 17, 2025
Step-by-step installation instructions for getting started...
Table of Contents:
• Prerequisites
• Installation Steps
• Verification

For documentation pages, we use TechArticle schema:

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "Installation Guide",
"description": "Step-by-step installation instructions",
"image": "https://docs.example.com/og-image.png",
"author": {
"@type": "Person",
"name": "Your Name",
"url": "https://example.com"
},
"publisher": {
"@type": "Organization",
"name": "Your Company",
"logo": {
"@type": "ImageObject",
"url": "https://docs.example.com/logo.png"
}
},
"datePublished": "2025-01-15T10:00:00Z",
"dateModified": "2025-02-17T10:00:00Z",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://docs.example.com/guide/installation"
},
"dependencies": "Node.js 18+, npm 9+",
"proficiencyLevel": "Beginner",
"about": {
"@type": "Thing",
"name": "Software Installation"
}
}
</script>

For the homepage/about:

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your Company",
"url": "https://docs.example.com",
"logo": "https://docs.example.com/logo.png",
"description": "Comprehensive documentation for developers",
"sameAs": [
"https://twitter.com/yourhandle",
"https://github.com/yourorg",
"https://linkedin.com/company/yourcompany"
],
"contactPoint": {
"@type": "ContactPoint",
"contactType": "Customer Support",
"email": "support@example.com",
"url": "https://example.com/support"
}
}
</script>

For navigation breadcrumbs:

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://docs.example.com"
}, {
"@type": "ListItem",
"position": 2,
"name": "Guides",
"item": "https://docs.example.com/guides"
}, {
"@type": "ListItem",
"position": 3,
"name": "Installation",
"item": "https://docs.example.com/guides/installation"
}]
}
</script>

For site-wide search:

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "My Documentation",
"url": "https://docs.example.com",
"potentialAction": {
"@type": "SearchAction",
"target": "https://docs.example.com/search?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}
</script>
starlightOptimizer({
seo: {
structuredData: {
enabled: true,
organization: {
name: 'Your Company',
logo: '/logo.png',
url: 'https://docs.example.com',
sameAs: [
'https://twitter.com/yourhandle',
'https://github.com/yourorg',
],
},
author: {
name: 'Your Name',
url: 'https://example.com',
},
breadcrumbs: true,
searchAction: true,
},
},
}),
ToolURLPurpose
Google Rich Results Testsearch.google.com/test/rich-resultsTest rich snippets
Schema Markup Validatorvalidator.schema.orgValidate JSON-LD
Google Search Consolesearch.google.com/search-consoleMonitor rich results

Control search engine crawling:

<!-- Default: Allow indexing -->
<meta name="robots" content="index, follow" />
<!-- Prevent indexing (draft pages) -->
<meta name="robots" content="noindex, nofollow" />
<!-- Index but don't follow links -->
<meta name="robots" content="index, nofollow" />

Per-page control:

---
title: Draft Documentation
robots: noindex, nofollow
---

Prevent duplicate content issues:

<!-- Automatically generated for every page -->
<link rel="canonical" href="https://docs.example.com/guide/installation" />

Why canonical matters:

  • ✅ Prevents duplicate content penalties
  • ✅ Consolidates ranking signals
  • ✅ Handles URL parameters (?utm_source=…)
  • ✅ Resolves http vs https
  • ✅ Resolves www vs non-www

For multi-language documentation:

<!-- Automatically generated if i18n enabled -->
<link rel="alternate" hreflang="en" href="https://docs.example.com/guide" />
<link rel="alternate" hreflang="es" href="https://docs.example.com/es/guide" />
<link rel="alternate" hreflang="fr" href="https://docs.example.com/fr/guide" />
<link rel="alternate" hreflang="x-default" href="https://docs.example.com/guide" />
<meta name="author" content="Your Name" />
<meta name="publisher" content="Your Company" />
<link rel="author" href="https://example.com/about" />
<!-- Optional, less important for modern SEO -->
<meta name="keywords" content="installation, setup, getting started, documentation" />

:::note Keywords Tag The keywords meta tag has minimal SEO impact today. Focus on content quality and headings instead. :::

SEO ranking factors include performance metrics:

MetricTargetOptimizer Impact
LCP (Largest Contentful Paint)< 2.5s✅ No degradation (100)
FID (First Input Delay)< 100ms✅ No degradation (100)
CLS (Cumulative Layout Shift)< 0.1✅ No degradation (100)
INP (Interaction to Next Paint)< 200ms✅ Maintained

How we maintain performance:

  • Async script loading
  • Minimal JavaScript (4 KB added)
  • No render-blocking resources
  • Optimized images
  • Efficient CSS

Google prioritizes mobile-first indexing:

<!-- Viewport meta tag (Starlight includes this) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Mobile-friendly features -->
- Responsive layout ✅
- Touch-friendly buttons (48x48px) ✅
- Readable font sizes (16px+) ✅
- No horizontal scrolling ✅

HTTPS is a ranking signal:

<!-- Canonical uses HTTPS -->
<link rel="canonical" href="https://docs.example.com/guide" />
<!-- Upgrade insecure requests -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />
PlatformImage SizeCharacter LimitNotes
Facebook1200x630Title: 100, Desc: 200Uses Open Graph
Twitter/X1200x675Title: 70, Desc: 200Uses Twitter Cards
LinkedIn1200x627Title: 200, Desc: 300Uses Open Graph
Discord1200x630UnlimitedUses Open Graph
Slack1200x630UnlimitedUses Open Graph
WhatsApp1200x630UnlimitedUses Open Graph
// Title: Keep under 60 characters for Google, 70 for Twitter
const title = "Installation Guide - My Docs"; // 30 chars ✅
// Description: 150-160 characters for Google, 200 for social
const description =
"Step-by-step installation instructions for getting started with our documentation platform. Covers prerequisites, setup, and verification.";
// 157 chars ✅

Test share previews before publishing:

Terminal window
# 1. Generate OG image
npm run generate-og-images
# 2. Test locally with ngrok
npx ngrok http 4321
# 3. Test share preview
# Paste ngrok URL into Facebook Debugger

Heading hierarchy:

# Page Title (H1) - Only one per page
Your main content...
## Section 1 (H2)
Content...
### Subsection 1.1 (H3)
Content...
### Subsection 1.2 (H3)
Content...
## Section 2 (H2)
Content...

Never skip heading levels:

❌ Bad:
# H1
### H3 (skipped H2)
✅ Good:
# H1
## H2
### H3

Link related pages:

For more information, see:
- [Configuration Guide](/astro-starlight-docs-template/guides/configuration)
- [Troubleshooting](/astro-starlight-docs-template/guides/troubleshooting)
- [Advanced Features](/astro-starlight-docs-template/features/overview)

Benefits:

  • ✅ Helps search engines discover pages
  • ✅ Distributes page authority
  • ✅ Improves user navigation
  • ✅ Increases time on site
![Installation steps diagram](./install-steps.png)

Alt text best practices:

  • ✅ Descriptive: “Installation steps diagram showing npm install command”
  • ❌ Generic: “Image” or “Screenshot”
  • ❌ Keyword stuffing: “install installation setup getting started npm node”

Good URL structure:

✅ /guides/installation
✅ /api/authentication
✅ /troubleshooting/common-errors
❌ /page?id=123
❌ /docs/v2/en/guides/getting-started-with-installation-guide
❌ /kategorie/installation-anleitung-fur-anfanger-schritt-fur-schritt

Best practices:

  • Keep URLs short and descriptive
  • Use hyphens, not underscores
  • Avoid unnecessary parameters
  • Use lowercase
  • Include keywords naturally

Monitor your documentation’s search performance:

Setup:

  1. Add property to Search Console
  2. Verify ownership (HTML file, DNS, or meta tag)
  3. Submit sitemap
  4. Monitor performance

Key metrics to track:

  • Total impressions
  • Total clicks
  • Average CTR (click-through rate)
  • Average position
  • Coverage issues
  • Core Web Vitals
  • Mobile usability

Automatically generated by @astrojs/sitemap:

astro.config.mjs
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://docs.example.com',
integrations: [
starlight({...}),
sitemap(),
starlightOptimizer({...}),
],
});

Submit sitemap to search engines:

  • Google: Search Console → Sitemaps → Add sitemap
  • Bing: Webmaster Tools → Sitemaps → Submit sitemap

Create public/robots.txt:

# Allow all crawlers
User-agent: *
Allow: /
# Sitemap location
Sitemap: https://docs.example.com/sitemap-index.xml
# Optional: Disallow specific paths
User-agent: *
Disallow: /draft/
Disallow: /private/
Terminal window
# 1. Verify image exists and is accessible
curl -I https://docs.example.com/og-image.png
# Should return 200 OK
# 2. Check meta tag
view-source:https://docs.example.com/guide
# Look for <meta property="og:image" content="..." />
# 3. Verify absolute URL
# ❌ content="/og-image.png"
# ✅ content="https://docs.example.com/og-image.png"
# 4. Clear social media cache
# Facebook Debugger → Scrape Again
# Twitter Card Validator → Preview card
Terminal window
# Test with Google Rich Results Test
https://search.google.com/test/rich-results
# Common errors:
# - Missing required field: Add "author", "datePublished", etc.
# - Invalid date format: Use ISO 8601 (2025-02-17T10:00:00Z)
# - Invalid URL: Use absolute URLs
# - Missing @context: Always include "@context": "https://schema.org"
Terminal window
# 1. Check robots.txt
curl https://docs.example.com/robots.txt
# Should NOT have: Disallow: /
# 2. Check meta robots
view-source:https://docs.example.com/guide
# Should have: <meta name="robots" content="index, follow" />
# 3. Submit to Google Search Console
# Manually request indexing for new pages
# 4. Check for noindex in frontmatter
# Remove: robots: noindex

Next Steps: