diff --git a/.claude/skills/docs/SKILL.md b/.claude/skills/docs/SKILL.md index 57c589f5..c69752a7 100644 --- a/.claude/skills/docs/SKILL.md +++ b/.claude/skills/docs/SKILL.md @@ -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` | diff --git a/.claude/skills/docs/references/seo.md b/.claude/skills/docs/references/seo.md new file mode 100644 index 00000000..c2b68435 --- /dev/null +++ b/.claude/skills/docs/references/seo.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 `` 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 diff --git a/site/CLAUDE.md b/site/CLAUDE.md index 63beb8d1..1bb0402d 100644 --- a/site/CLAUDE.md +++ b/site/CLAUDE.md @@ -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 diff --git a/site/src/consts.ts b/site/src/consts.ts index aa98dd07..371b721e 100644 --- a/site/src/consts.ts +++ b/site/src/consts.ts @@ -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'; diff --git a/site/src/content/docs/concepts/architecture.mdx b/site/src/content/docs/concepts/architecture.mdx index f7278205..28b4b890 100644 --- a/site/src/content/docs/concepts/architecture.mdx +++ b/site/src/content/docs/concepts/architecture.mdx @@ -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'; diff --git a/site/src/content/docs/concepts/skins.mdx b/site/src/content/docs/concepts/skins.mdx index 82242b4f..0b4acbdd 100644 --- a/site/src/content/docs/concepts/skins.mdx +++ b/site/src/content/docs/concepts/skins.mdx @@ -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'; diff --git a/site/src/content/docs/concepts/ui-components.mdx b/site/src/content/docs/concepts/ui-components.mdx index 8dff5324..2ecef5e2 100644 --- a/site/src/content/docs/concepts/ui-components.mdx +++ b/site/src/content/docs/concepts/ui-components.mdx @@ -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'; diff --git a/site/src/content/docs/how-to/installation.mdx b/site/src/content/docs/how-to/installation.mdx index df2d92f3..b8cac2ff 100644 --- a/site/src/content/docs/how-to/installation.mdx +++ b/site/src/content/docs/how-to/installation.mdx @@ -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 Roadmap for more details on what's coming. -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**. + +Video.js is a **React video player component library** — composable primitives, hooks, and TypeScript types for building accessible, customizable players with minimal bundle size. + + +Video.js is an **HTML video player built on custom elements** — lightweight, framework-free components for building accessible, customizable players with minimal bundle size. + + Answer the questions below to get started quickly with your first embed code. ## Choose your JS framework diff --git a/site/src/content/docs/reference/buffering-indicator.mdx b/site/src/content/docs/reference/buffering-indicator.mdx index da96861a..720ae84f 100644 --- a/site/src/content/docs/reference/buffering-indicator.mdx +++ b/site/src/content/docs/reference/buffering-indicator.mdx @@ -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"; diff --git a/site/src/content/docs/reference/controls.mdx b/site/src/content/docs/reference/controls.mdx index 097d9811..a65bdad1 100644 --- a/site/src/content/docs/reference/controls.mdx +++ b/site/src/content/docs/reference/controls.mdx @@ -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"; diff --git a/site/src/content/docs/reference/fullscreen-button.mdx b/site/src/content/docs/reference/fullscreen-button.mdx index 5d742ac1..b75674fa 100644 --- a/site/src/content/docs/reference/fullscreen-button.mdx +++ b/site/src/content/docs/reference/fullscreen-button.mdx @@ -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"; diff --git a/site/src/content/docs/reference/mute-button.mdx b/site/src/content/docs/reference/mute-button.mdx index 46cf64a5..525f7f5b 100644 --- a/site/src/content/docs/reference/mute-button.mdx +++ b/site/src/content/docs/reference/mute-button.mdx @@ -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"; diff --git a/site/src/content/docs/reference/pip-button.mdx b/site/src/content/docs/reference/pip-button.mdx index cd44954f..6a593fa6 100644 --- a/site/src/content/docs/reference/pip-button.mdx +++ b/site/src/content/docs/reference/pip-button.mdx @@ -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"; diff --git a/site/src/content/docs/reference/play-button.mdx b/site/src/content/docs/reference/play-button.mdx index c346e747..4c4aae9c 100644 --- a/site/src/content/docs/reference/play-button.mdx +++ b/site/src/content/docs/reference/play-button.mdx @@ -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"; diff --git a/site/src/content/docs/reference/poster.mdx b/site/src/content/docs/reference/poster.mdx index a391c798..c90c4e34 100644 --- a/site/src/content/docs/reference/poster.mdx +++ b/site/src/content/docs/reference/poster.mdx @@ -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"; diff --git a/site/src/content/docs/reference/seek-button.mdx b/site/src/content/docs/reference/seek-button.mdx index c68d6aa0..3ad08e17 100644 --- a/site/src/content/docs/reference/seek-button.mdx +++ b/site/src/content/docs/reference/seek-button.mdx @@ -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"; diff --git a/site/src/content/docs/reference/time.mdx b/site/src/content/docs/reference/time.mdx index 757b333d..fbc17e67 100644 --- a/site/src/content/docs/reference/time.mdx +++ b/site/src/content/docs/reference/time.mdx @@ -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"; diff --git a/site/src/layouts/Base.astro b/site/src/layouts/Base.astro index 856e57c6..2dece24c 100644 --- a/site/src/layouts/Base.astro +++ b/site/src/layouts/Base.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}`; --- diff --git a/site/src/layouts/Docs.astro b/site/src/layouts/Docs.astro index 9a2af2f6..d09ce389 100644 --- a/site/src/layouts/Docs.astro +++ b/site/src/layouts/Docs.astro @@ -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'; --- - + diff --git a/site/src/pages/blog/[...page].astro b/site/src/pages/blog/[...page].astro index 7fcda5bc..7937fab9 100644 --- a/site/src/pages/blog/[...page].astro +++ b/site/src/pages/blog/[...page].astro @@ -44,12 +44,12 @@ const jsonLdSchema = createBlogCollectionSchema({ }); --- - +

Video.js Blog

- {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'}

diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index caf124c7..5021ad3a 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -25,8 +25,8 @@ const poster = `${VJS8_DEMO_VIDEO.poster}?time=0`; >

The Open Source Player for the Web

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

diff --git a/site/src/utils/jsonLd/schemas.ts b/site/src/utils/jsonLd/schemas.ts index 79920087..35ecdfc3 100644 --- a/site/src/utils/jsonLd/schemas.ts +++ b/site/src/utils/jsonLd/schemas.ts @@ -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',