30 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Overview
The Video.js v10 documentation site is an Astro-based static site generator with React islands for interactivity. The site serves documentation, blog posts, and interactive demos for the Video.js v10 library.
Key architectural feature: Multi-framework documentation — the same content generates separate routes for different framework/style combinations (e.g., HTML + CSS, React + CSS), allowing framework-specific documentation from shared MDX sources.
Commands
From site/ directory:
| Command | Purpose |
|---|---|
pnpm dev |
Start dev server at localhost:4321 |
pnpm build |
Build production site to ./dist/ |
pnpm api-docs |
Regenerate API reference JSON files |
pnpm test |
Run all tests once |
pnpm test:watch |
Run tests in watch mode |
pnpm test:ui |
Run Vitest with web UI |
pnpm test:coverage |
Generate coverage report |
pnpm astro ... |
Run Astro CLI (e.g., pnpm astro check) |
From monorepo root:
pnpm dev:site— Start site dev serverpnpm build:site— Build site
Running single test file:
pnpm test sidebar.test.ts
Astro MCP Server
An Astro MCP server is likely available. Always use Astro best practices and standard patterns for maintainability. Consult the MCP for Astro-specific guidance when working with:
- Astro components and layouts
- Content collections
- Routing patterns
- Integrations
- Build optimizations
Following Astro conventions ensures consistency and makes the codebase easier to maintain.
Tailwind v4 Configuration & Gotchas
This project uses Tailwind v4 with a custom configuration. Standard Tailwind tokens may not be available.
CRITICAL: Always Check globals.css First
Before using any Tailwind utility class, read src/styles/globals.css to verify:
- Custom color tokens (e.g.,
dark-110,light-100, not standard Tailwind colors) - Custom text size tokens (e.g.,
text-h1,text-h2, not standardtext-4xl,text-5xl) - Custom tracking values
- Custom font weight values
- Available custom variants
- And possibly more
Custom Variant: intent: (NOT hover: or focus-visible:)
Use the intent: variant instead of hover: and focus-visible::
<!-- ✅ CORRECT -->
<button class="intent:bg-dark-80">Click me</button>
<!-- ❌ WRONG -->
<button class="hover:bg-dark-80">Click me</button>
The intent: variant is defined as:
@custom-variant intent (&:hover, &:focus-within);
Arbitrary Tailwind: Last Resort Only
Avoid arbitrary variants like [&:hover] or text-[pink]. Use them only as a last resort.
Prefer inline styles when Tailwind utilities don't exist:
<!-- ✅ BETTER: Inline style -->
<div style="transform: rotate(45deg)">Content</div>
<!-- ❌ WORSE: Arbitrary variant -->
<div class="transform-[rotate(45deg)]">Content</div>
<div class="[transform:rotate(45deg)]">Content</div>
Use clsx for Class Concatenation
Always use clsx (or cn helper if available) for conditional classes:
import clsx from 'clsx';
<button class={clsx(
'text-base bg-dark-100',
isActive && 'bg-dark-80',
isPrimary ? 'text-bright-yellow' : 'text-light-100'
)}>
Click me
</button>
Avoiding Arbitrary Values
Prefer token-based utilities or inline styles over arbitrary values:
<!-- ✅ CORRECT: Use token if available -->
<div class="min-h-30">Content</div>
<!-- ✅ CORRECT: Inline style for non-token values -->
<div style="min-height: 120px">Content</div>
<!-- ❌ WRONG: Arbitrary value -->
<div class="min-h-[120px]">Content</div>
For responsive/dark mode with non-token values, use CSS custom properties:
<!-- ✅ CORRECT: Custom property bridge -->
<div
style="--md-min-h: 120px"
class="md:min-h-(--md-min-h)"
>Content</div>
<!-- ❌ WRONG: Arbitrary value in variant -->
<div class="md:min-h-[120px]">Content</div>
This limits the classnames Tailwind must generate.
Project Structure
site/
├── src/
│ ├── components/ # Astro + React components
│ │ └── docs/
│ │ ├── api-reference/ # API reference Astro components
│ │ └── demos/ # Interactive component demos (see below)
│ ├── content/ # Content collections (blog/, docs/, authors.json)
│ ├── layouts/ # Page layouts (Base, Blog, Docs, Markdown)
│ ├── pages/ # Route pages (file-based routing)
│ ├── content/generated-component-reference/ # Generated component reference JSON (gitignored)
│ ├── content/generated-util-reference/ # Generated util reference JSON (gitignored)
│ ├── stores/ # Nanostores for cross-island state
│ ├── styles/ # Global CSS, Tailwind imports
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # Utilities and helpers
│ │ └── docs/ # Documentation-specific utilities
│ │ ├── sidebar.ts # Sidebar filtering and navigation
│ │ ├── routing.ts # Docs URL building and redirects
│ │ └── __tests__/ # Tests for docs utilities
│ ├── consts.ts # Site-wide constants
│ ├── content.config.ts # Content collection schemas
│ ├── docs.config.ts # Documentation sidebar structure
│ └── test-setup.ts # Vitest setup file
├── scripts/
│ └── api-docs-builder/ # Generates API reference from TypeScript
├── public/ # Static assets (served untransformed)
├── integrations/ # Custom Astro integrations
│ └── llms-markdown.ts # LLM-optimized markdown generation
├── astro.config.mjs # Astro configuration
├── tsconfig.json # TypeScript config with path aliases
└── vitest.config.ts # Test configuration
Interactive Demos
Reference pages include live, interactive demos for each component. Demos are framework-specific (React, HTML) and style-specific (CSS).
Directory Structure
src/components/docs/demos/
├── Demo.astro # Shared shell (live preview + tabbed source code)
├── HtmlDemo.astro # Renders raw HTML via set:html
└── {component}/{framework}/{style}/
├── BasicUsage.tsx # React: component (+ .css)
├── BasicUsage.astro # HTML: Astro wrapper (renders HTML, imports CSS, bundles script)
├── BasicUsage.html # HTML: markup only, no <style> or <script>
├── BasicUsage.css # HTML: styles (imported by .astro wrapper for live demo)
└── BasicUsage.ts # HTML: side-effect imports for custom element registration
CSS Scoping with BEM
Demos use BEM class names for scoping. Block = {component}-{variant}, element = __{part}:
.play-button-basic /* block */
.play-button-basic__button /* element */
React .css and HTML .css files for the same demo should use identical BEM names.
React Demos
A .tsx component + .css file. Rendered as an Astro island via client:idle, displayed as source via ?raw:
import BasicUsageDemo from "@/components/docs/demos/play-button/react/css/BasicUsage";
import basicUsageTsx from "@/components/docs/demos/play-button/react/css/BasicUsage.tsx?raw";
import basicUsageCss from "@/components/docs/demos/play-button/react/css/BasicUsage.css?raw";
<Demo files={[
{ title: "App.tsx", code: basicUsageTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageCss, lang: "css" },
]}>
<BasicUsageDemo client:idle />
</Demo>
HTML Demos
Four files per demo: .html (markup only), .css (styles), .ts (custom element registration), and .astro (wrapper that ties them together). The .astro wrapper is needed because only Astro <script> tags go through Vite's bundling pipeline — MDX <script> tags compile as JSX and aren't bundled.
.astro wrapper (imports CSS for live demo, renders HTML, bundles the .ts script):
---
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
import html from './BasicUsage.html?raw';
import './BasicUsage.css';
---
<HtmlDemo html={html} />
<script>
import './BasicUsage.ts';
</script>
MDX usage:
import BasicUsageDemoHtml from "@/components/docs/demos/play-button/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/play-button/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/play-button/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/play-button/html/css/BasicUsage.ts?raw";
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
State Reflection in HTML Demos
HTML custom elements expose state via data-* attributes. Use CSS to toggle labels:
<media-play-button class="play-button-basic__button">
<span class="show-when-paused">Play</span>
<span class="show-when-playing">Pause</span>
</media-play-button>
.play-button-basic__button .show-when-paused { display: none; }
.play-button-basic__button .show-when-playing { display: none; }
.play-button-basic__button[data-paused] .show-when-paused { display: inline; }
.play-button-basic__button:not([data-paused]) .show-when-playing { display: inline; }
The React equivalent uses the render prop: render={(props, state) => <button {...props}>{state.paused ? 'Play' : 'Pause'}</button>}.
Video Attributes
All demo videos use autoplay muted playsinline loop (React: autoPlay muted playsInline loop).
Multi-Framework Documentation Architecture
Framework/Style Combinations
Documentation is generated for multiple framework and style combinations from the same MDX source files.
Current support (defined in src/types/docs.ts):
- Frameworks:
html,react - Styles:
css(more may be added)
URL pattern:
/docs/framework/{framework}/{...slug}/
Style handling: Style is a client-side preference stored in localStorage per-framework (vjs_docs_style_html, vjs_docs_style_react). The StyleInit.astro component reads localStorage before paint and sets html[data-style]. CSS rules control content visibility via [data-for-style] attributes on <StyleCase> wrapped content.
Example:
src/content/docs/how-to/installation.mdxgenerates:/docs/framework/html/how-to/installation//docs/framework/react/how-to/installation/
Content Restriction Mechanisms
1. Within MDX content:
Use <FrameworkCase> or <StyleCase> components to show framework/style-specific content:
<FrameworkCase for="react">
Use `useState` to manage state.
</FrameworkCase>
<FrameworkCase for="html">
Use `data-` attributes to manage state.
</FrameworkCase>
2. In sidebar config (src/docs.config.ts):
Restrict entire guides to specific frameworks:
const sidebar: Sidebar = [
{
sidebarLabel: 'Getting started',
contents: [
{ slug: 'how-to/installation' }, // Available to all frameworks
{
slug: 'how-to/react-hooks',
frameworks: ['react'] // Only visible when viewing React docs
},
],
},
];
Note: Style restrictions on sidebar items are no longer supported. All docs are visible to all styles; use <StyleCase> within docs to show style-specific content.
Sidebar Configuration
Structure (src/docs.config.ts):
- Export a
sidebarconstant of typeSidebar - Hierarchical: Sections contain Guides
- Each Guide has:
slug: Path relative tosrc/content/docs/(without.mdx)sidebarLabel(optional): Override display nameframeworks(optional): Restrict to specific frameworksdevOnly(optional): Show only in development mode
Example:
export const sidebar: Sidebar = [
{
sidebarLabel: 'Components',
contents: [
{ slug: 'reference/play-button' },
{ slug: 'reference/mute-button', sidebarLabel: 'Mute' },
],
},
];
Documentation Utilities
Key Utility Functions (src/utils/docs/)
sidebar.ts — Sidebar filtering and navigation:
filterSidebar(): Filter sidebar by framework, remove empty sectionsfindFirstGuide(): Get first available guide for frameworkfindGuideBySlug(): Search sidebar recursively for a guidegetAdjacentGuides(): Get prev/next guides for navigationgetSectionsForGuide(): Get breadcrumb trail to a guide
routing.ts — URL building and redirect logic:
buildDocsUrl(): Construct docs URLs from framework and slugresolveIndexRedirect(): Intelligent redirect for index pages- Validates framework from URL params or cookie preferences
- Falls back to defaults when invalid
Docs Routing Pattern
Nested index pages handle redirects at each level:
/docs/ → redirect to first guide
/docs/framework/ → redirect to first guide
/docs/framework/{framework}/ → redirect to first guide
/docs/framework/{framework}/{slug} → render guide
Each index page uses resolveIndexRedirect() to determine where to redirect based on:
- URL params (framework)
- User preferences (framework from cookies)
- Defaults (when invalid or missing)
Style is not part of the URL. Style preference is stored per-framework in localStorage and applied client-side via CSS.
Content Collections
Defined in src/content.config.ts using Astro's Content Collections API.
IMPORTANT: Only MDX Files Supported
We only support .mdx files, NOT .md files.
All content must be written in MDX format to support:
- React components within content
- Framework/style conditional rendering (
<FrameworkCase>,<StyleCase>) - Custom typography components
- Interactive examples
Blog Collection (src/content/blog/)
Filename convention: YYYY-MM-DD-slug.mdx
- Date prefix automatically removed from slug
- Example:
2024-01-15-new-release.mdx→ slug:new-release, URL:/blog/new-release/
Schema:
{
title: string;
description: string;
pubDate: Date; // From filename date prefix
authors: string[]; // Reference to authors.json
canonical?: string; // Canonical URL override
devOnly?: boolean; // Show only in development
ogImage?: ImageMetadata | string; // Local image or external URL
twitterImage?: ImageMetadata | string; // Falls back to ogImage
}
OG images go in src/assets/blog/{date-slug}/og.png. Reference from frontmatter with a relative path:
ogImage: '../../assets/blog/2026-03-10-my-post/og.png'
Docs Collection (src/content/docs/)
Subdirectories:
how-to/— Outcome-focused guides (per Diátaxis framework)concepts/— Understanding-focused guidesreference/— API documentation
Schema:
{
title: string;
description: string;
frameworkTitle?: { // Per-framework title overrides
html?: string;
react?: string;
};
updatedDate?: Date; // From git history
}
Authors Collection (src/content/authors.json)
{
[key: string]: {
name: string;
shortName: string;
bio?: string;
avatar?: string;
socialLinks?: {
x?: string;
bluesky?: string;
mastodon?: string;
github?: string;
linkedin?: string;
website?: string;
};
}
}
Git Integration
src/utils/gitService.ts uses simple-git to enrich content with metadata:
- Blog posts:
pubDatefrom filename date prefix - All content:
updatedDatefrom last modification
State Management with Nanostores
Why Nanostores? Astro's island architecture means each React component with client:load is an isolated React root. React Context doesn't work across islands, so we use Nanostores for cross-island state.
Store locations (src/stores/):
preferences.ts: User framework/style preferences (persisted to localStorage)homePageDemos.ts: Home page demo statetabs.ts: Tab component stateinstallation.ts: Installation page state (renderer, skin, install method)
Usage pattern:
import { useStore } from '@nanostores/react';
import { $preferences } from '@/stores/preferences';
function MyComponent() {
const prefs = useStore($preferences);
// ...
}
Testing
Configuration (vitest.config.ts)
{
globals: true, // No import needed for describe, it, expect
environment: 'jsdom', // Browser-like environment
setupFiles: ['./src/test-setup.ts'], // Imports @testing-library/jest-dom
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/utils/**', 'src/components/**', 'src/types/**', 'scripts/api-docs-builder/src/**'],
exclude: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', '**/test/**'],
},
}
Test Organization
Tests are colocated with source code in __tests__/ directories:
src/utils/docs/
├── sidebar.ts
├── routing.ts
└── __tests__/
├── sidebar.test.ts
└── routing.test.ts
Testing Patterns
Mock framework/style configuration:
vi.mock('@/types/docs', async () => {
const actual = await vi.importActual('@/types/docs');
return {
...actual,
FRAMEWORK_STYLES: { html: ['css'], react: ['css'] },
};
});
Test complex utilities:
- Sidebar filtering with nested sections
- Route resolution and redirect logic
- Framework/style validation
Technology Stack
- Astro 5.14.4: Static site generation with island architecture
- React 19: Client-side interactive components (
client:load) - React Compiler: Enabled via
babel-plugin-react-compilertargeting React 19 - Tailwind v4: CSS utility classes via
@tailwindcss/vite - Nanostores 1.0.1: Cross-island state
- Base UI 1.2.0: Headless accessible components
- Algolia DocSearch v4: Search via Algolia-hosted indexes (docs + blog)
- Shiki 3.13.0: Syntax highlighting
- Vitest 3.2.4: Testing framework
- clsx: Class name concatenation utility
API Reference Generation
Source of truth:
internal/design/site/api-docs-builder.mdThe design spec is the ground-truth for the entire pipeline — discovery, extraction, JSON schemas, reference model, and rendered output. Any changes to the api-docs-builder must be reflected in the spec. When implementation diverges from the spec, the spec wins.
The builder (scripts/api-docs-builder/) extracts type information from TypeScript sources and generates JSON for two kinds of reference:
- Component references →
src/content/generated-component-reference/{name}.json - Util references →
src/content/generated-util-reference/{slug}.json
Generated files are gitignored and regenerated automatically on pnpm dev and pnpm build, or manually via pnpm api-docs.
Usage in MDX
import ComponentReference from '@/components/docs/api-reference/ComponentReference.astro';
import UtilReference from '@/components/docs/api-reference/UtilReference.astro';
<ComponentReference component="PlayButton" />
<UtilReference util="usePlayer" />
Search: Algolia DocSearch v4
Config: src/search.config.ts — Algolia app ID, API key, and index names.
Component: src/components/Search.tsx — React component loaded via client:load in the NavBar.
Styles: src/styles/docsearch.css — CSS variable overrides mapping DocSearch theming to site tokens (imported via globals.css).
How it works:
DocSearchReact component provides the trigger button, modal, and keyboard shortcut (Cmd+K)- Two indexes queried via the
indicesprop:videojs_docs(filtered by current framework) andvideojs_blog(unfiltered) - No build-time indexing — search queries hit the Algolia API directly
Data attributes used by the crawler:
data-search-content— marks searchable content regionsdata-search-ignore— excludes elements from search indexingdata-framework,data-site,data-category— facet/filter attributesdata-llms-*attributes are for the LLM markdown integration (unrelated to search)
Environment Variables
The site uses OAuth for authentication and Mux for video management. Required variables are needed for auth features to work; the site degrades gracefully without them.
Required for authentication:
| Variable | Purpose |
|---|---|
OAUTH_CLIENT_ID |
OAuth client ID |
OAUTH_CLIENT_SECRET |
OAuth client secret |
OAUTH_REDIRECT_URI |
OAuth callback URL |
OAUTH_URL |
OAuth provider base URL |
SESSION_COOKIE_PASSWORD |
Encryption key for iron-session cookies |
Optional:
| Variable | Purpose |
|---|---|
MUX_API_URL |
Override Mux API endpoint (defaults to https://api.mux.com) |
MUX_TOKEN_ID |
Mux API token ID (for server-side health checks) |
MUX_TOKEN_SECRET |
Mux API token secret (for server-side health checks) |
SENTRY_AUTH_TOKEN |
Sentry error tracking auth token |
Authentication & Mux Integration
OAuth and Mux integration exist to support the video uploader on the installation page (src/components/installation/MuxUploaderPanel.tsx). This is the only consumer of the auth system.
Key files:
src/middleware/index.ts— Validates and refreshes OAuth sessions on every request viairon-sessionsrc/utils/auth.ts— Session encryption, JWKS verification, token refreshsrc/pages/api/auth/callback.ts— OAuth callback endpointsrc/actions/auth.ts—initiateLogin(),logout()server actionssrc/actions/mux.ts—createDirectUpload(),getUploadStatus(),getAssetStatus(),listAssets(),getAsset()server actions
How sessions work:
- Middleware decrypts session cookie, verifies access token via JWKS
- If expired, automatically refreshes using the refresh token
- Populates
context.locals.user(safe to render) andcontext.locals.accessToken(server-only, never expose to client) - Invalid/corrupted sessions are silently cleared
Action gating: All mux.* actions (except createDirectUpload) return 401 without a valid session. createDirectUpload handles its own auth so the client can detect UNAUTHORIZED and show a login UI.
MDX Processing Plugins
Four plugins transform MDX content during build. Registered in astro.config.mjs:
remarkConditionalHeadings (src/utils/remarkConditionalHeadings.js)
Walks the MDX AST and tracks headings inside <FrameworkCase> / <StyleCase> components, attaching conditional metadata (which frameworks/styles a heading belongs to). Also reads <ComponentReference> and <UtilReference> component props, loads the generated JSON, and injects heading entries so API reference sections appear in the table of contents. Outputs to frontmatter.conditionalHeadings.
remarkReadingTime (src/utils/remarkReadingTime.mjs)
Calculates reading time and injects frontmatter.minutesRead (text) and frontmatter.readingTimeMinutes (number).
rehypePrepareCodeBlocks (src/utils/rehypePrepareCodeBlocks.js)
Tags <code> children of <pre> with a codeBlock property, and marks <pre> blocks with hasFrame: true when inside a <TabsPanel> JSX component. This controls code block styling (framed vs. standalone).
shikiTransformMetadata (src/utils/shikiTransformMetadata.js)
Shiki transformer that extracts title="..." from code fence metadata, enabling titled code blocks:
```tsx title="Example.tsx"
Custom Astro Integration: LLM Markdown
Location: integrations/llms-markdown.ts
Generates LLM-optimized markdown files and a llms.txt index after build.
How it works:
- Scans all built HTML pages for elements with
[data-llms-content] - Strips elements with
[data-llms-ignore]from the content - Converts remaining HTML to markdown via Turndown
- Writes
.mdfiles alongside built HTML - Generates
llms.txtindex grouped by framework/style
Data attributes for content authors:
| Attribute | Purpose |
|---|---|
data-llms-content |
Mark an element's content for LLM markdown extraction |
data-llms-ignore |
Exclude an element (and its children) from LLM output |
data-llms-description |
Description text for the llms.txt index entry |
data-llms-sort |
Sort key for ordering entries in the index |
TypeScript Configuration
Path aliases (tsconfig.json):
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
Import examples:
import { sidebar } from '@/docs.config';
import type { Sidebar } from '@/types/docs';
import { filterSidebar } from '@/utils/docs/sidebar';
Strict mode enabled:
noUncheckedIndexedAccess: trueexactOptionalPropertyTypes: true
Key Architecture Patterns
1. Recursive Sidebar Filtering
Sidebar filtering is recursive because sections can contain guides or nested sections:
export function filterSidebar(
framework: SupportedFramework,
sidebarToFilter?: Sidebar,
): Sidebar {
const root = sidebarToFilter ?? sidebar;
return root
.map((item) => filterItem(item, framework))
.filter(isNotFalsy);
}
2. Type Guards for Framework/Style Validation
Defined in src/types/docs.ts:
export const FRAMEWORK_STYLES = {
html: ['css'],
react: ['css'],
} as const;
export function isValidFramework(value: unknown): value is SupportedFramework {
return typeof value === 'string' && value in FRAMEWORK_STYLES;
}
export function isValidStyleForFramework(
framework: SupportedFramework,
style: unknown,
): style is AnySupportedStyle {
return typeof style === 'string'
&& FRAMEWORK_STYLES[framework].includes(style as any);
}
3. Git-Enriched Content Metadata
Content collections automatically enrich metadata from git history:
// In content.config.ts
const blog = defineCollection({
loader: globWithParser({
pattern: '**/*.mdx',
base: './src/content/blog',
async parseData(frontmatter, fileUrl) {
const filePath = fileURLToPath(fileUrl);
const updatedDate = await getLastModifiedDate(filePath);
return { ...frontmatter, updatedDate };
},
}),
});
4. Island Architecture with React
Each React component with client:load is an independent React root:
---
import Tabs from '@/components/Tabs.tsx';
import Search from '@/components/Search/Search.tsx';
---
<Tabs client:load /> <!-- Independent React root #1 -->
<Search client:load /> <!-- Independent React root #2 -->
Consequence: React Context doesn't work across islands. Use Nanostores instead.
MDX Component Typography
Location: src/components/typography/
Standard MDX elements (headings, paragraphs, lists, etc.) are defined here and used across all MDX layouts (blog, docs, markdown pages).
Usage in layouts:
---
import defaultMarkdownComponents from '@/components/typography/defaultMarkdownComponents';
---
<Content components={{ ...defaultMarkdownComponents }} />
Important Development Notes
Writing Documentation
Read src/content/docs/how-to/write-guides.mdx for comprehensive guide-writing instructions.
Key points:
- Use
.mdxfiles only (not.md) - Use
<FrameworkCase>and<StyleCase>for framework/style-specific content - Follow Diátaxis framework: how-to vs. concept guides
- Add new guides to
src/docs.config.tssidebar - Use
devOnly: truefor internal documentation
Search
Search is powered by Algolia DocSearch v4 and queries the Algolia API directly — no build-time indexing step needed. Search works in both dev and production as long as the Algolia account is provisioned. See the "Search: Algolia DocSearch v4" section above for details.
Adding Framework/Style Support
To add a new framework or style:
- Update
FRAMEWORK_STYLESinsrc/types/docs.ts - Update type definitions (
SupportedFramework,AnySupportedStyle) - Add corresponding page routes in
src/pages/docs/framework/[framework]/ - Update sidebar filtering logic if needed (usually automatic)
- Update tests to include new framework/style
Blog Post Naming
CRITICAL: Blog post filenames MUST be date-prefixed:
YYYY-MM-DD-slug.mdx
The date prefix is automatically removed from the slug during content collection transformation by src/utils/globWithParser.ts.
Example:
- File:
2024-01-15-new-release.mdx - Slug:
new-release - URL:
/blog/new-release/
Development-Only Content
Use devOnly: true in frontmatter or sidebar config to hide content in production:
// In docs.config.ts
{ slug: 'how-to/write-guides', devOnly: true }
---
title: Internal Documentation
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
- Create MDX file in
src/content/docs/{how-to|concepts|reference}/your-guide.mdx - Add frontmatter with
titleanddescription - Add to sidebar in
src/docs.config.ts - Optional: Restrict to specific frameworks/styles
- Test with
pnpm devand verify all framework/style combinations
Running Tests for Specific Utility
# Run sidebar tests
pnpm test sidebar.test.ts
# Run in watch mode
pnpm test:watch sidebar.test.ts
# With UI
pnpm test:ui
Debugging Redirect Logic
The resolveIndexRedirect() function in src/utils/docs/routing.ts returns a reason field explaining why a particular redirect was chosen:
const result = resolveIndexRedirect({ preferences, params });
console.log(result.reason); // e.g., "using preference framework and style"
Checking TypeScript
pnpm astro check
This runs Astro's built-in TypeScript checker across .astro, .ts, and .tsx files.