mirror of
https://github.com/zoriya/v10.git
synced 2026-08-13 17:40:12 +00:00
feat(site): framework-specific SEO metadata for docs (#541)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
78e8e3a8e1
commit
e936ea4e44
@@ -37,6 +37,7 @@ This skill adds writing guidance on top of those. When in doubt, the source file
|
||||
| Package README from scratch | `templates/readme.md` |
|
||||
| Code example conventions | `patterns/code-examples.md` |
|
||||
| Error documentation patterns | `patterns/error-docs.md` |
|
||||
| SEO metadata and keyword targeting | `references/seo.md` |
|
||||
| Review workflow (multi-agent) | `review/workflow.md` |
|
||||
| Review checklist (single-agent) | `review/checklist.md` |
|
||||
| Review agent prompts | `review/agents.md` |
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# SEO Metadata Conventions
|
||||
|
||||
Conventions for page titles, meta descriptions, and structured data across the Video.js site.
|
||||
|
||||
## Target keywords
|
||||
|
||||
These are the search terms we optimize for. Work them into titles, descriptions, body content, and anchor text where they fit naturally.
|
||||
|
||||
| Priority | Keyword | Where it appears |
|
||||
| -------- | ----------------------------- | -------------------------------------------------------------- |
|
||||
| High | **open source video player** | Every page title suffix, homepage |
|
||||
| High | **React video player** | React docs title suffix, installation intro, meta descriptions |
|
||||
| High | **HTML video player** | HTML docs title suffix, installation intro, meta descriptions |
|
||||
| Medium | **video player** | Site description, body content, anchor text |
|
||||
| Medium | **video player components** | Installation page, component reference descriptions |
|
||||
| Medium | **accessible video player** | Component descriptions, installation intro |
|
||||
| Medium | **video player performance** | Site description, architecture page, installation description |
|
||||
| Medium | **lightweight video player** | Site description, installation intro |
|
||||
| Medium | **fast video player** | Architecture page, performance-related content |
|
||||
| Low | **streaming** | Site description, installation description |
|
||||
| Low | **customizable video player** | Component descriptions, skins pages |
|
||||
|
||||
**Framework-specific keywords** are the highest priority for docs pages because each framework gets its own URL (`/docs/framework/react/...` and `/docs/framework/html/...`), creating natural keyword-targeting opportunities.
|
||||
|
||||
**Performance keywords** are natural fits — v10 is built around minimal bundle sizes, CSS-driven animations, and smooth rendering. Use "performance", "lightweight", and "fast" in descriptions for architecture, installation, and component pages.
|
||||
|
||||
## Page titles
|
||||
|
||||
Pattern: `PAGE | Video.js | Open Source [Framework] Video Player`
|
||||
|
||||
| Context | Format | Example |
|
||||
| ------------ | ---------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| Docs (React) | `Page \| Video.js \| Open Source React Video Player` | `PlayButton \| Video.js \| Open Source React Video Player` |
|
||||
| Docs (HTML) | `Page \| Video.js \| Open Source HTML Video Player` | `media-play-button \| Video.js \| Open Source HTML Video Player` |
|
||||
| Non-docs | `Page \| Video.js \| Open Source Video Player` | `Blog \| Video.js \| Open Source Video Player` |
|
||||
| Homepage | `Video.js \| Open Source Video Player` | — |
|
||||
|
||||
### How it works
|
||||
|
||||
- `SITE_TITLE` (`Video.js`) — brand segment, always present
|
||||
- `SEO_SUFFIX` (`Open Source Video Player`) — default suffix for non-docs pages
|
||||
- `Base.astro` accepts an optional `suffix` prop (defaults to `SEO_SUFFIX`)
|
||||
- `Docs.astro` overrides suffix with `Open Source ${FRAMEWORK_LABELS[framework]} Video Player`
|
||||
- Title construction: `pageTitle | SITE_TITLE | suffix` (or just `SITE_TITLE | suffix` for homepage)
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Keep total title under ~60 characters — Google truncates beyond that
|
||||
- Target keywords should appear before the truncation point
|
||||
- Don't add "Video.js" or "video player" to the page-specific segment — the suffix handles it
|
||||
|
||||
## Meta descriptions
|
||||
|
||||
### Docs pages
|
||||
|
||||
Docs descriptions come from MDX frontmatter `description` + an automatic framework suffix appended by `Docs.astro`:
|
||||
|
||||
```
|
||||
${description} — Video.js ${Framework} Video Player.
|
||||
```
|
||||
|
||||
**Writing good descriptions:**
|
||||
|
||||
- Describe the benefit, not just the feature: "Accessible play/pause button with keyboard support" not "A button component for playing and pausing"
|
||||
- Don't include "Video.js" or version numbers — the suffix handles branding
|
||||
- Keep the frontmatter description under ~120 chars so the full description (with suffix) stays under ~160 chars
|
||||
- Use action words: "Accessible", "Customizable", "Composable", "Lightweight", "Fast", "Performant"
|
||||
|
||||
### Non-docs pages
|
||||
|
||||
Set directly in the page or layout. Include "video player" and relevant keywords naturally.
|
||||
|
||||
## Body content keywords
|
||||
|
||||
The installation page (`how-to/installation.mdx`) uses `<FrameworkCase>` to show framework-specific intro text with target keywords:
|
||||
|
||||
- **React:** "React video player component library"
|
||||
- **HTML:** "HTML video player built on custom elements"
|
||||
|
||||
When adding new high-traffic landing pages, include framework-specific keywords in body content — not just metadata.
|
||||
|
||||
## Structured data (JSON-LD)
|
||||
|
||||
`createTechArticleSchema` in `site/src/utils/jsonLd/schemas.ts` generates JSON-LD for docs pages. It includes:
|
||||
|
||||
```ts
|
||||
about: {
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Video.js',
|
||||
applicationCategory: 'MultimediaApplication',
|
||||
operatingSystem: 'Web',
|
||||
}
|
||||
```
|
||||
|
||||
This helps search engines categorize docs as documentation for a specific software product.
|
||||
|
||||
## Internal linking
|
||||
|
||||
Use keyword-rich anchor text for internal links instead of generic text:
|
||||
|
||||
| Instead of | Use |
|
||||
| -------------------- | --------------------------------------------------------- |
|
||||
| "click here" | descriptive text about the target page |
|
||||
| "learn more" | what the reader will learn, e.g. "customize player skins" |
|
||||
| bare component names | "video player controls" or "PlayButton component" |
|
||||
|
||||
High-value linking opportunities:
|
||||
|
||||
- Homepage → framework installation pages (anchor text: "React video player", "HTML video player")
|
||||
- Cross-links between related component reference pages
|
||||
- Links from concept pages to relevant reference pages with descriptive text
|
||||
@@ -889,6 +889,10 @@ devOnly: true
|
||||
---
|
||||
```
|
||||
|
||||
## SEO Metadata
|
||||
|
||||
When writing content for the site, especially page titles and descriptions, follow the conventions in `.claude/skills/docs/references/seo.md` for the full keyword list and guidelines.
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New Docs Guide
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
export const SITE_TITLE = 'Video.js 10';
|
||||
export const SITE_DESCRIPTION = `For over 15 years, Video.js has been the world's web video player. Now rebuilt in v10 for modern development and performance.`;
|
||||
export const SITE_TITLE = 'Video.js';
|
||||
export const SEO_SUFFIX = 'Open Source Video Player';
|
||||
export const SITE_DESCRIPTION = `The open-source video player for React and HTML. Lightweight, accessible components built for performance and streaming.`;
|
||||
export const GITHUB_REPO_URL = 'https://github.com/videojs/v10/';
|
||||
export const DISCORD_INVITE_URL = 'https://discord.gg/JBqHh485uF';
|
||||
export const THEME_KEY = 'vjs-site-theme';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: Understanding the three-tier architecture of Video.js v10 - State, UI, and Renderers
|
||||
description: How Video.js structures its video player — state management, UI components, and platform renderers
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Skins
|
||||
description: Understanding skins in Video.js v10 - packaged sets of UI components and styles
|
||||
description: Packaged sets of video player UI components and styles that you can customize or replace
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: UI components
|
||||
description: Understanding Video.js v10's primitive-based component architecture
|
||||
description: Primitive-based component architecture for building accessible, composable video player controls
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Get started quickly with Video.js by building your first embed code
|
||||
description: Install Video.js and build your first player with streaming support and accessible controls
|
||||
---
|
||||
|
||||
import JSPicker from '@/components/installation/JSPicker.astro';
|
||||
@@ -21,7 +21,13 @@ import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs.tsx';
|
||||
Video.js v10 is currently in _beta_. The API may evolve with [feedback🙏](https://github.com/videojs/v10/issues). See the [Changelog](https://github.com/videojs/v10/blob/main/CHANGELOG.md)\[todo\] for recent updates and the <DocsLink slug="concepts/v10-roadmap">Roadmap</DocsLink> for more details on what's coming.
|
||||
</Aside>
|
||||
|
||||
Video.js is **audio and video player components** — intentionally built to achieve **minimal bundle sizes** while providing **framework-specific customization** and a **great viewer experience**.
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Video.js is a **React video player component library** — composable primitives, hooks, and TypeScript types for building accessible, customizable players with minimal bundle size.
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
Video.js is an **HTML video player built on custom elements** — lightweight, framework-free components for building accessible, customizable players with minimal bundle size.
|
||||
</FrameworkCase>
|
||||
|
||||
Answer the questions below to get started quickly with your first embed code.
|
||||
|
||||
## Choose your JS framework
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: BufferingIndicator
|
||||
frameworkTitle:
|
||||
html: media-buffering-indicator
|
||||
description: A component that displays a loading indicator when media is buffering
|
||||
description: Loading indicator that displays when the video player is buffering or waiting for data
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Controls
|
||||
frameworkTitle:
|
||||
html: media-controls
|
||||
description: Containers for composing and auto-hiding player controls
|
||||
description: Container component for composing and auto-hiding video player controls on user interaction
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: FullscreenButton
|
||||
frameworkTitle:
|
||||
html: media-fullscreen-button
|
||||
description: A button component for entering and exiting fullscreen mode
|
||||
description: Accessible fullscreen toggle button with keyboard support and state reflection
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: MuteButton
|
||||
frameworkTitle:
|
||||
html: media-mute-button
|
||||
description: A button component for muting and unmuting audio playback
|
||||
description: Accessible mute/unmute button with keyboard support and volume state reflection
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: PiPButton
|
||||
frameworkTitle:
|
||||
html: media-pip-button
|
||||
description: A button component for entering and exiting picture-in-picture mode
|
||||
description: Accessible picture-in-picture toggle button with keyboard support and state reflection
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: PlayButton
|
||||
frameworkTitle:
|
||||
html: media-play-button
|
||||
description: A button component for playing and pausing media playback
|
||||
description: Accessible play/pause button with keyboard support and customizable rendering
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Poster
|
||||
frameworkTitle:
|
||||
html: media-poster
|
||||
description: Poster image component that stays visible until playback starts
|
||||
description: Poster image component that displays a thumbnail until video playback starts
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: SeekButton
|
||||
frameworkTitle:
|
||||
html: media-seek-button
|
||||
description: A button component for seeking media playback forward or backward by a specified number of seconds
|
||||
description: Accessible seek button for skipping forward or backward by a configurable number of seconds
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Time
|
||||
frameworkTitle:
|
||||
html: media-time
|
||||
description: Components for displaying and composing media time information
|
||||
description: Time display components for showing current time, duration, and remaining time in a video player
|
||||
---
|
||||
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Font } from 'astro:assets';
|
||||
import type { ImageMetadata } from 'astro';
|
||||
|
||||
import { SITE_TITLE } from '@/consts';
|
||||
import { SEO_SUFFIX, SITE_TITLE } from '@/consts';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
|
||||
@@ -17,17 +17,17 @@ interface Props {
|
||||
description: string;
|
||||
image?: ImageMetadata;
|
||||
canonical?: string;
|
||||
suffix?: string;
|
||||
}
|
||||
const { canonical } = Astro.props;
|
||||
const { canonical, suffix } = Astro.props;
|
||||
const canonicalURL = canonical || new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title, description, image } = Astro.props;
|
||||
|
||||
const fullTitle = Array.isArray(title)
|
||||
? `${title.filter((t) => t !== SITE_TITLE).join(' | ')} | ${SITE_TITLE}`
|
||||
: title === SITE_TITLE
|
||||
? title
|
||||
: `${title} | ${SITE_TITLE}`;
|
||||
const seoSuffix = suffix ?? SEO_SUFFIX;
|
||||
const pageTitle = Array.isArray(title) ? title.join(' | ') : title;
|
||||
const fullTitle =
|
||||
pageTitle === SITE_TITLE ? `${SITE_TITLE} | ${seoSuffix}` : `${pageTitle} | ${SITE_TITLE} | ${seoSuffix}`;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
||||
@@ -9,7 +9,7 @@ import FilmGrain from '@/components/FilmGrain';
|
||||
import Footer from '@/components/Footer.astro';
|
||||
import FooterEasterEgg from '@/components/FooterEasterEgg.astro';
|
||||
import NavBar from '@/components/NavBar/NavBar.astro';
|
||||
import type { SupportedFramework } from '@/types/docs';
|
||||
import { FRAMEWORK_LABELS, type SupportedFramework } from '@/types/docs';
|
||||
import { getDocTitle } from '@/utils/docs/title';
|
||||
import Base from './Base.astro';
|
||||
|
||||
@@ -25,10 +25,11 @@ const { doc, framework, slug } = Astro.props;
|
||||
const allDocs = await getCollection('docs');
|
||||
const docTitles = new Map(allDocs.map((d) => [d.id, getDocTitle(d, framework)]));
|
||||
|
||||
const seoCategory = `${FRAMEWORK_LABELS[framework]} Video Player`;
|
||||
const docsSidebarId = 'docs-sidebar';
|
||||
---
|
||||
|
||||
<Base title={[getDocTitle(doc, framework), 'Docs']} description={doc.data.description}>
|
||||
<Base title={getDocTitle(doc, framework)} suffix={`Open Source ${FRAMEWORK_LABELS[framework]} Video Player`} description={`${doc.data.description} — Video.js ${seoCategory}.`}>
|
||||
<Fragment slot="priority-head">
|
||||
<StyleInit />
|
||||
</Fragment>
|
||||
|
||||
@@ -44,12 +44,12 @@ const jsonLdSchema = createBlogCollectionSchema({
|
||||
});
|
||||
---
|
||||
|
||||
<Blog title="Blog" description="Articles, announcements, news, updates, and more">
|
||||
<Blog title="Blog" description="News and updates from the Video.js open-source video player project">
|
||||
<JsonLd slot="head" schema={jsonLdSchema} />
|
||||
<header class="mb-10 md:mb-20 w-full max-w-3xl mx-auto">
|
||||
<h1 class="text-h4 md:text-h2 mb-2">Video.js Blog</h1>
|
||||
<p class="text-lg font-medium md:text-h5 text-dark-40 dark:text-light-40">
|
||||
{page.data.length === 0 ? 'Coming soon' : 'Articles, announcements, news, updates, and more'}
|
||||
{page.data.length === 0 ? 'Coming soon' : 'News and updates from the Video.js open-source video player project'}
|
||||
</p>
|
||||
</header>
|
||||
<div class="grid gap-10 w-full max-w-3xl mx-auto">
|
||||
|
||||
@@ -25,8 +25,8 @@ const poster = `${VJS8_DEMO_VIDEO.poster}?time=0`;
|
||||
>
|
||||
<h1 class="text-h4 md:text-h1 text-balance lg:text-pretty">The Open Source Player for the Web</h1>
|
||||
<p class="md:text-lg text-balance max-w-100">
|
||||
For over 15 years, Video.js has been the world’s web video player. Now rebuilt in v10 for modern development
|
||||
and performance.
|
||||
The open-source video player for React and HTML. Lightweight, accessible components built for performance and
|
||||
streaming.
|
||||
</p>
|
||||
</div>
|
||||
<div class="vjs relative z-10 w-full -mb-11 md:-mb-21 lg:-mb-32 grid grid-cols-1 grid-rows-1">
|
||||
|
||||
@@ -30,6 +30,12 @@ export function createTechArticleSchema(params: {
|
||||
...(params.wordCount && { wordCount: params.wordCount }),
|
||||
...(params.readingTime && { timeRequired: `PT${params.readingTime}M` }),
|
||||
...(params.articleSection && { articleSection: params.articleSection }),
|
||||
about: {
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Video.js',
|
||||
applicationCategory: 'MultimediaApplication',
|
||||
operatingSystem: 'Web',
|
||||
},
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
@@ -112,7 +118,7 @@ export function createBlogCollectionSchema(params: {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'Video.js Blog',
|
||||
description: 'Articles, announcements, news, updates, and more',
|
||||
description: 'News and updates from the Video.js open-source video player project',
|
||||
url: params.url,
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
|
||||
Reference in New Issue
Block a user