npm package: @farmtrace/seo
Description: Comprehensive SEO toolkit for Next.js applications with programmatic SEO, JSON-LD schema, and content generation support
Latest version: 1.0.3

# @farmtrace/seo

A comprehensive, plugin-based SEO toolkit for React applications. Handles metadata generation, JSON-LD structured data, sitemaps, breadcrumbs, and programmatic SEO — all configured with a single `createSEO()` call.

Works with any React framework: Next.js, Remix, Vite, Gatsby, Astro, or plain React.

## Features

- **Framework-agnostic** — works with Next.js, Remix, Vite, Gatsby, Astro, or plain React
- **Zero-config defaults** — only `name` and `domain` required, everything else has smart defaults
- **Plugin architecture** — extend with custom schemas, templates, page types, and middleware
- **JSON-LD schema** — Organization, WebSite, Article, FAQ, BreadcrumbList, SoftwareApplication, HowTo, Person
- **Metadata API** — generates structured metadata objects compatible with Next.js `Metadata` and any other framework
- **Page registry** — register pages for automatic sitemap generation and breadcrumbs
- **AutoSEO component** — React component that auto-injects JSON-LD from conventions
- **Programmatic SEO** — templates, content hashing, and uniqueness scoring for generated pages
- **March 2026 Core Update compliant** — E-E-A-T Person schema, Information Gain scoring, content uniqueness validation
- **Tree-shakeable** — subpath imports for each module

## Installation

```bash
# npm
npm install @farmtrace/seo

# bun
bun add @farmtrace/seo

# pnpm
pnpm add @farmtrace/seo
```

### Peer Dependencies

```json
{
  "react": ">=18"
}
```

Next.js is optional — the package works without it.

## Quick Start

### 1. Initialize

Call `createSEO()` once in your app (e.g., in `lib/seo/config.ts`):

```ts
import { createSEO } from '@farmtrace/seo';

createSEO({
  name: 'MyApp',
  domain: 'myapp.com',
  tagline: 'Build better apps',
  social: {
    twitter: '@myapp',
    linkedin: 'https://linkedin.com/company/myapp',
  },
  images: {
    logo: '/logo.png',
    ogDefault: '/og-image.jpg',
  },
});
```

### 2. Generate Metadata

```ts
// Works with any framework — returns a plain Metadata object
import { generateMetadata as genMeta } from '@farmtrace/seo';
import type { Metadata } from '@farmtrace/seo';

function getPageMetadata(): Metadata {
  return genMeta({
    title: 'About Us',
    description: 'Learn about our mission and team.',
    path: '/about',
    type: 'generic',
  });
}
```

### 3. Add JSON-LD Schema

```tsx
import { AutoSEO } from '@farmtrace/seo';

export default function AboutPage() {
  return (
    <AutoSEO path="/about" schemaOptions={{ name: 'About Us', description: '...' }}>
      <main>{/* page content */}</main>
    </AutoSEO>
  );
}
```

## API Reference

### Core

| Export | Description |
|--------|-------------|
| `createSEO(options)` | Initialize the SEO system. Returns an `SEOInstance` with runtime extension methods. |
| `configureSEO(config)` | Legacy configuration (use `createSEO` instead). |
| `definePlugin(plugin)` | Type-safe plugin definition helper. |
| `getSiteConfig()` | Get the resolved `SiteConfig`. |
| `getTitleTemplates()` | Get all title templates as `Record<string, string>`. |
| `getDescriptionTemplates()` | Get all description templates as `Record<string, string>`. |
| `getKeywords()` | Get the keyword dictionary. |
| `addTemplate(kind, key, template)` | Register a title or description template at runtime. |
| `resolveTemplate(kind, key, vars)` | Interpolate a registered template with variables. |
| `addSchema(type, generator)` | Register a custom schema generator. |
| `addMiddleware(fn)` | Add metadata middleware. |
| `addPageType(type, config)` | Register a custom page type. |

### Metadata

| Export | Description |
|--------|-------------|
| `generateMetadata(options)` | Generate a Next.js `Metadata` object. |
| `generateProgrammaticMetadata(options)` | Metadata for programmatic/template pages. |
| `generateBlogPostMetadata(options)` | Specialized blog post metadata. |
| `generateIntegrationMetadata(options)` | Specialized integration page metadata. |
| `generateFeatureMetadata(options)` | Specialized feature page metadata. |
| `generateUseCaseMetadata(options)` | Specialized use-case page metadata. |
| `generateLocationMetadata(options)` | Specialized location page metadata. |
| `mergeMetadata(...sources)` | Deep-merge multiple metadata objects. |
| `createMetadataGenerator(defaults)` | Create a reusable metadata generator with defaults. |
| `formatTitle(title)` | Apply the default title template. |
| `stripHtml(str)` | Remove HTML tags from a string. |
| `truncateText(text, max)` | Truncate text to a max length with ellipsis. |

### Schema (JSON-LD)

| Export | Description |
|--------|-------------|
| `generateOrganizationSchema()` | Organization schema from site config. |
| `generateWebSiteSchema()` | WebSite schema with search action. |
| `generateWebPageSchema(path, options)` | WebPage (or subtype) schema. |
| `generateArticleSchema(path, options)` | Article schema with author and dates. |
| `generatePersonSchema(person)` | Person schema with E-E-A-T signals. |
| `generateBreadcrumbSchema(items)` | BreadcrumbList schema from items. |
| `generateBreadcrumbsFromPath(path)` | Auto-generate breadcrumbs from a URL path. |
| `generateFAQSchema(items)` | FAQPage schema. |
| `generateSoftwareSchema(options)` | SoftwareApplication schema. |
| `generateHowToSchema(options)` | HowTo schema with steps. |
| `wrapSchema(schema)` | Wrap a schema with `@context`. |
| `generateSchemaGraph(schemas)` | Combine schemas into a `@graph`. |
| `schemaToJsonString(schema)` | Serialize schema to JSON string. |
| `isValidSchema(schema)` | Validate a schema has required `@type`. |

### Page Registry

| Export | Description |
|--------|-------------|
| `registerStaticPages(pages)` | Register known pages. |
| `registerDynamicPages(generator)` | Register a dynamic page generator. |
| `getStaticPages()` | Get all static pages. |
| `getDynamicPages()` | Resolve and get all dynamic pages. |
| `getAllPages()` | Get all pages (static + dynamic). |
| `findPageByPath(path)` | Look up a page by its path. |
| `getPageConfig(path, overrides?)` | Get config or auto-generate from conventions. |
| `generatePageConfigFromPath(path)` | Generate page config from URL conventions. |
| `generateSitemapFromRegistry(baseUrl)` | Generate sitemap entries from all registered pages. |
| `generateBreadcrumbsFromRegistry(path)` | Generate breadcrumb trail from registry. |
| `getRelatedPages(path, limit?)` | Find related pages by relevance scoring. |

### Auto-SEO

| Export | Description |
|--------|-------------|
| `AutoSEO` | React component — auto-injects JSON-LD schema markup. |
| `createPageMetadata(path, options?)` | Convention-based metadata generation. |
| `generateAutoSchema(path, options?)` | Convention-based schema generation. |
| `createFullPageSEO(path, options)` | Returns both `{ metadata, schema }` in one call. |

### Constants

| Constant | Description |
|----------|-------------|
| `PAGE_TYPES` | Maps page types → Schema.org types (`home` → `WebSite`, `article` → `Article`, etc.) |
| `SEO_LIMITS` | Title/description character limits (`{ min, max, optimal }`) |
| `REVALIDATION` | ISR revalidation intervals by content type (seconds) |
| `SITEMAP_CONFIG` | Sitemap defaults: max URLs, priorities, change frequencies |
| `CONTENT_REQUIREMENTS` | Minimum word counts, required sections, unique content ratio |

### Utilities

| Export | Description |
|--------|-------------|
| `getCanonicalUrl(path)` | Build canonical URL from path and site config. |
| `getAlternateUrls(path)` | Generate alternate language URLs. |
| `validateTitle(title)` | Check title against `SEO_LIMITS`. |
| `validateDescription(desc)` | Check description against `SEO_LIMITS`. |
| `generateContentHash(content)` | Generate a hash for content uniqueness tracking. |

## Subpath Imports

Import only the module you need to keep bundles minimal:

```ts
import { createSEO, definePlugin } from '@farmtrace/seo/core';
import { generateArticleSchema } from '@farmtrace/seo/schema';
import { generateMetadata } from '@farmtrace/seo/metadata';
import { registerStaticPages } from '@farmtrace/seo/registry';
import { AutoSEO } from '@farmtrace/seo/auto-seo';
import type { SEOPlugin } from '@farmtrace/seo/types';
import { PAGE_TYPES } from '@farmtrace/seo/constants';
import { getCanonicalUrl } from '@farmtrace/seo/utils';
```

## Plugin System

Plugins bundle related functionality — schemas, templates, page types, and middleware:

```ts
import { definePlugin, createSEO } from '@farmtrace/seo';

const analyticsPlugin = definePlugin({
  name: 'analytics',
  middleware: [
    (metadata, ctx) => {
      // Add tracking parameters to all pages
      metadata['other'] = { ...metadata['other'], 'analytics-page-type': ctx.type };
      return metadata;
    },
  ],
});

const ecommercePlugin = definePlugin({
  name: 'ecommerce',
  schemas: {
    product: (data) => ({
      '@type': 'Product',
      name: data?.name,
      description: data?.description,
      offers: { '@type': 'Offer', price: data?.price, priceCurrency: 'USD' },
    }),
  },
  pageTypes: {
    product: { schemaType: 'Product', ogType: 'product', priority: 0.8 },
    collection: { schemaType: 'CollectionPage', ogType: 'website', priority: 0.7 },
  },
  templates: {
    title: { product: '%s | Shop', collection: '%s Collection | Shop' },
    description: { product: 'Buy %s at great prices.' },
  },
  setup: (seo) => {
    console.log(`Ecommerce plugin loaded for ${seo.config.site.name}`);
  },
});

createSEO({
  name: 'MyShop',
  domain: 'myshop.com',
  plugins: [analyticsPlugin, ecommercePlugin],
});
```

## Page Registry & Sitemap

```ts
import {
  registerStaticPages,
  registerDynamicPages,
  generateSitemapFromRegistry,
} from '@farmtrace/seo';

// Register your pages
registerStaticPages([
  { path: '/', title: 'Home', description: 'Welcome', type: 'home', priority: 1.0 },
  { path: '/about', title: 'About', description: 'About us', type: 'about', parent: '/' },
  { path: '/blog', title: 'Blog', description: 'Latest posts', type: 'blog-listing' },
]);

registerDynamicPages({
  pattern: '/blog/[slug]',
  type: 'blog-post',
  generate: async () => {
    const posts = await fetchPosts();
    return posts.map(p => ({
      path: `/blog/${p.slug}`,
      title: p.title,
      description: p.excerpt,
      type: 'blog-post' as const,
    }));
  },
});

// In your sitemap.ts
export default async function sitemap() {
  return generateSitemapFromRegistry('https://myshop.com');
}
```

## Architecture

```
src/
├── index.ts          # Public API (barrel exports)
├── types.ts          # All type definitions
├── constants.ts      # PAGE_TYPES, SEO_LIMITS, REVALIDATION, etc.
├── utils.ts          # URL helpers, validators, hashing
├── core/
│   ├── state.ts      # Singleton config store
│   ├── factory.ts    # createSEO() + configureSEO()
│   ├── getters.ts    # Config accessor functions
│   ├── templates.ts  # Template registration + interpolation
│   ├── plugins.ts    # definePlugin() + applyPlugin()
│   └── extensions.ts # Runtime schema/middleware/pageType registration
├── schema/
│   ├── types.ts      # Schema.org TypeScript interfaces
│   ├── generators.ts # All generate*Schema() functions
│   └── utils.ts      # wrapSchema, generateSchemaGraph, etc.
├── metadata/
│   ├── generator.ts  # generateMetadata, generateProgrammaticMetadata
│   ├── specialized.ts # Blog, integration, feature, use-case, location generators
│   └── utils.ts      # mergeMetadata, formatTitle, stripHtml, truncateText
├── registry/
│   ├── store.ts      # Page registration + lookup
│   ├── conventions.ts # Path-based page config inference
│   └── sitemap.ts    # Sitemap + breadcrumbs + related pages
└── auto-seo/
    ├── metadata.ts   # createPageMetadata (convention-based)
    ├── schema.ts     # generateAutoSchema (convention-based)
    └── component.tsx # <AutoSEO> React component + createFullPageSEO
```

## Requirements

- **React** ≥ 18
- **TypeScript** ≥ 5 (recommended)
- **Next.js** ≥ 14 (optional — for `generateMetadata` integration)

## License

MIT