npm package: @ripp/seo-head
Description: TypeScript library for managing SEO meta tags in the document head with automatic platform-specific tag mapping.
Author: Charly Rippenkroeger
Latest version: 1.0.0

# @ripp/seo-head

A Zero-Dependency, SSR-friendly, TypeScript library for managing SEO meta tags. 
Automatically maps standard SEO keys to platform-specific tags (Open Graph, Twitter/X, etc.).

Also see the [Playground UI](coming-soon) and [UI Quick Start](UI_QUICK_START.md)

## Installation

```bash
npm install @ripp/seo-head
```

## Quick Start

```ts
import { SeoHead } from "@ripp/seo-head";

// Initialize once with site defaults
const seoHead = new SeoHead({
  defaults: {
    siteName: "My Site",
    robots: "index, follow",
  },
});

// Update tags as you navigate
seoHead.update({
  title: "Home Page",
  description: "Welcome to my awesome page",
  canonical: "https://example.com/",
  image: "https://example.com/og-image.jpg",
});
```

That's it! The library automatically creates and updates `<meta>`, `<link>`, and `<title>` tags for:
- Standard SEO (title, description, canonical)
- Open Graph (Facebook, LinkedIn)
- Twitter/X Cards
- And more...

## API Methods

```ts
// Get current SEO context
const current = seoHead.getCurrent();

// Reset to defaults (useful for navigation)
seoHead.reset();

// Clear all SEO tags
seoHead.clear();

// Clear specific key
seoHead.clearKey('title');

// Inspect current state (for debugging)
const { context, tags } = seoHead.inspect();
```

## Advanced Features

### Title Templates

```ts
// String template
const seoHead = new SeoHead({
  titleTemplate: '%s | My Site',
});
seoHead.update({ title: 'Home' }); // → "Home | My Site"

// Functional template
const seoHead = new SeoHead({
  titleTemplate: (title, context) => {
    return context.type === 'article' 
      ? `${title} - Blog`
      : `${title} | Site`;
  },
});
```

### URL Pattern Matching

Match routes dynamically using glob patterns or RegExp:

```ts
const seoHead = new SeoHead({
  // Exact match (backward compatible)
  urlMapping: {
    '/about': { type: 'website' },
  },
  // Pattern matching
  urlPatterns: [
    { pattern: '/blog/*', context: { type: 'article' } },
    { pattern: /^\/user\/\d+$/, context: { type: 'profile' } },
  ],
});
```

### Validation & Diagnostics

```ts
const seoHead = new SeoHead({
  validate: true, // Enable all validations
  onValidationWarning: (warning) => {
    console.warn(`SEO Issue: ${warning.key}`, warning.message);
  },
});

// Or enable specific validations
const seoHead = new SeoHead({
  validate: {
    title: true,
    description: true,
    images: false,
    canonical: false,
  },
});
```

Validates:
- Title length (10-60 chars recommended)
- Description length (50-160 chars recommended)
- Missing images on articles
- Missing alt text on images
- Missing canonical on articles

### PWA & Performance Tags

```ts
seoHead.update({
  // PWA
  themeColor: '#00695c',
  manifestUrl: '/manifest.json',
  appleTouchIcon: '/apple-touch-icon.png',
  appleStatusBarStyle: 'black-translucent',
  
  // Performance
  preconnect: ['https://fonts.googleapis.com'],
  dnsPrefetch: ['https://fonts.gstatic.com'],
  preload: [
    { href: '/fonts/main.woff2', as: 'font', type: 'font/woff2', crossorigin: 'anonymous' },
  ],
});
```

### Event Callbacks

```ts
const seoHead = new SeoHead({
  onUpdate: (context) => {
    console.log('SEO updated:', context);
  },
  onTagsChanged: ({ added, removed, updated }) => {
    console.log('Tags changed:', { added, removed, updated });
  },
});
```

### JSON-LD Structured Data

```ts
seoHead.update({
  title: "Article Title",
  jsonLdScripts: [
    {
      "@context": "https://schema.org",
      "@type": "Article",
      headline: "Article Title",
      datePublished: "2024-01-01",
    },
  ],
});
```

### Custom Tag Mappings

```ts
const seoHead = new SeoHead({
  tagMapping: {
    customKey: [
      {
        tag: "meta",
        selector: 'meta[name="custom-key"]',
        attrs: { name: "custom-key", content: (value) => value },
      },
    ],
  },
});
```

### Fallback to Existing DOM Elements

```ts
const seoHead = new SeoHead({
  fallbackTitle: true,        // Use existing <title> if no title provided
  fallbackDescription: true,  // Use existing meta description
  fallbackIcon: true,         // Use existing favicon
});
```

## Features

- **Automatic tag mapping**: One object updates multiple platform-specific tags (OG, X/Twitter, etc.)
- **Title templates**: Format titles consistently with templates or functions
- **URL pattern matching**: Dynamic route-based SEO with glob patterns or RegExp
- **Validation**: Built-in SEO best practice validation with warnings
- **PWA support**: Theme colors, manifest, and Apple-specific tags
- **Performance optimization**: Preconnect, DNS prefetch, and preload tags
- **Event callbacks**: React to SEO updates with custom handlers
- **JSON-LD support**: Built-in structured data handling
- **API methods**: Get, reset, clear, and inspect SEO state
- **Lazy loading**: Elements created only when needed
- **Memory efficient**: Cleans up unused references automatically
- **Type-safe**: Full TypeScript support with comprehensive types

## Use Cases

### Single Page Applications (SPAs)
Update SEO tags dynamically as users navigate between routes without page reloads.

```ts
// React Router example
router.subscribe((route) => {
  seoHead.update({
    title: route.meta.title,
    description: route.meta.description,
    canonical: `https://example.com${route.path}`,
  });
});
```

### Server-Side Rendering (SSR)
Set SEO tags during server-side rendering for optimal initial page load and crawler support.

```ts
// Next.js, Remix, or similar
export async function loader() {
  const data = await fetchPageData();
  seoHead.update({
    title: data.title,
    description: data.description,
    image: data.ogImage,
  });
  return { data };
}
```

### Content Management Systems
Manage SEO for dynamic content like blog posts, products, or articles with consistent tag generation.

```ts
// Blog post example
function renderPost(post) {
  seoHead.update({
    title: post.title,
    description: post.excerpt,
    type: "article",
    contentAuthor: post.author.name,
    contentPublishedAt: post.publishedAt,
    jsonLdScripts: [{
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      headline: post.title,
      datePublished: post.publishedAt,
    }],
  });
}
```