# @vigilkids/cms-nuxt

Nuxt module for product-agnostic OneX CMS integration. It registers the CMS runtime configuration, auto-imported composables, Nuxt server endpoints, preview support, cache revalidation, route-policy URL generation, and optional `@nuxtjs/sitemap` integration.

The package owns the CMS client boundary and resource route policy. Consuming products own Nuxt page shells and choose the public URL shape through module options.

## Install

```bash
pnpm add @vigilkids/cms-nuxt @vigilkids/cms-client
```

## Peer Dependencies

- `nuxt >=3.15.0`
- `@vigilkids/cms-client ^0.3.1`
- `@nuxtjs/sitemap` is optional and only required when `cms.sitemap.enabled` is `true`

## Module Setup

Configure the module with the `cms` key in `nuxt.config.ts`. Do not create CMS `runtimeConfig` values by hand; the module derives the server and public runtime config from these options.

```typescript
export default defineNuxtConfig({
  modules: ['@vigilkids/cms-nuxt'],

  cms: {
    apiUrl: process.env.NUXT_CMS_API_URL ?? '',
    productCode: process.env.NUXT_CMS_PRODUCT_CODE ?? '',
    defaultLocale: 'en',
    webhookSecret: process.env.NUXT_CMS_WEBHOOK_SECRET ?? '',

    routes: {
      resources: {
        index: '/resources',
        category: '/resources/{category_slug}',
        article: '/resources/{category_slug}/{slug}',
        author: '/resources/authors/{slug}',
      },
      sourceResources: {
        index: '/',
        category: '/{category_slug}',
        article: '/{category_slug}/{slug}',
        author: '/authors/{slug}',
      },
      locale: {
        defaultLocale: 'en',
        prefixes: {
          'zh-CN': 'zh',
        },
        includeDefaultLocale: false,
      },
    },

    localeMap: {
      'zh-CN': 'zh',
    },

    content: {
      articleCollectionPath: '/api/cms/articles',
      cmsProxyPath: '/cms-proxy',
    },

    categories: {
      preload: true,
    },

    preview: {
      enabled: true,
      path: '/api/preview',
      cookieName: '__preview_token',
      cookieMaxAge: 1800,
    },

    revalidate: {
      enabled: true,
      path: '/api/_revalidate',
    },

    sitemap: {
      enabled: true,
      locales: ['en', 'zh-CN'],
      sourcePath: '/api/cms/sitemap-urls',
    },
  },
})
```

## Module Options

| Option | Default | Purpose |
| --- | --- | --- |
| `apiUrl` | `''` | CMS API base URL used by `CmsClient`, server routes, and SSR fetches. |
| `productCode` | `''` | Sent as `X-Product-Code` when present. |
| `defaultLocale` | `'en'` | Public default locale used by composables and route policy. |
| `webhookSecret` | `''` | Server-only HMAC secret for the revalidate endpoint. |
| `routes.resources` | built-in defaults | Public resource URL patterns generated by `useCmsRoutes`, preview, revalidate, and sitemap. |
| `routes.sourceResources` | built-in defaults | CMS API resource URL patterns used to parse preview and sitemap input before rebuilding public URLs. |
| `routes.locale` | default locale policy | Locale prefix policy for generated public URLs. |
| `content.articleCollectionPath` | `'/api/cms/articles'` | Nuxt server endpoint for normalized article collection queries. |
| `content.cmsProxyPath` | `'/cms-proxy'` | Nuxt server proxy prefix used by client-side CMS fetches. |
| `categories.preload` | `false` | Fetches `/categories` during module setup and exposes the result in public runtime config. |
| `preview` | enabled by default | Preview route and preview cookie behavior. |
| `revalidate` | enabled by default | Signed ISR cache invalidation endpoint. |
| `redirects` | enabled by default | CMS redirect middleware with priority-aware matching and configurable in-memory cache TTL. |
| `sitemap` | disabled by default | `@nuxtjs/sitemap` provider integration and sitemap source endpoint path. |
| `localeMap` | `{}` | Maps public locale codes to CMS API locale codes. |
| `localePrefixes` | `{}` | Additional prefix input merged into `routes.locale.prefixes`. |

## Resource Route Policy

Resource Route Policy is the package's public URL extension point. It keeps the CMS integration product-neutral: the package knows how to build and parse resource routes, while each product chooses its page structure.

Default resource patterns are:

```typescript
const defaultResourcePatterns = {
  index: '/',
  category: '/{category_slug}',
  article: '/{category_slug}/{slug}',
  author: '/authors/{slug}',
} as const
```

`routes.resources` defines public site paths. `routes.sourceResources` defines the path shape returned by CMS preview and sitemap payloads when that source path differs from the public site path. `routes.locale` controls locale prefixes:

```typescript
export default defineNuxtConfig({
  cms: {
    routes: {
      resources: {
        article: '/learn/{category_slug}/{slug}',
      },
      sourceResources: {
        article: '/{category_slug}/{slug}',
      },
      locale: {
        defaultLocale: 'en',
        prefixes: { fr: 'fr' },
        includeDefaultLocale: false,
      },
    },
  },
})
```

`useCmsRoutes()` exposes the resolved policy and route builder:

```vue
<script setup lang="ts">
const cmsRoutes = useCmsRoutes()

const articlePath = cmsRoutes.build('article', {
  category_slug: 'guides',
  slug: 'getting-started',
  locale: 'fr',
}, true)
</script>
```

A new product should change only:

- `cms.routes.resources`, `cms.routes.sourceResources`, and `cms.routes.locale`
- Nuxt page shells that map those URL patterns to UI components

The CMS client, preview endpoint, revalidation endpoint, category preload, fetch proxy, and sitemap source stay inside the package.

## Content Endpoints

The module registers no-cache Nuxt server endpoints from the configured paths.

| Endpoint | Method | Behavior |
| --- | --- | --- |
| `content.articleCollectionPath` | `GET` | Returns `Paginated<ArticleListItem>` with `locale`, `category_slug`, `q`, `page`, and `page_size` query support. Search results are normalized into article cards. |
| `${content.cmsProxyPath}/**` | any | Proxies client-side CMS requests to `apiUrl`, forwarding `X-Product-Code` and preview token headers when present. |
| `sitemap.sourcePath` | `GET` | Returns Nuxt sitemap entries for `?locale=...` using route policy and CMS sitemap data. |
| `preview.path` | `GET` | Reads preview input, sets the preview cookie, and redirects to the route-policy-generated resource path. |
| `revalidate.path` | `POST` | Verifies `x-webhook-signature` and clears matching Nitro route cache entries. |

## Categories

Set `categories.preload` to `true` when category navigation or route guards need category data at render time. The module fetches `${apiUrl}/categories` during setup and exposes the data to `useCmsCategories()`.

```vue
<script setup lang="ts">
const { currentCategories, getCategoryPath, categoryExists } = useCmsCategories()
</script>
```

Use `useCategories()` when the page should fetch categories through `@vigilkids/cms-client` with `useAsyncData`.

## Preview

Preview mode is enabled unless `preview.enabled` is `false`.

```http
GET /api/preview?token=preview-token&resource=article&slug=guides/getting-started&locale=en
```

The preview endpoint:

- reads `token`, `resource` or `resource_type`, `slug`, `locale`, and additional route params from the query string
- parses `slug` with `routes.sourceResources` when possible
- fetches article detail when the public article pattern requires `category_slug`
- sets the configured preview cookie
- redirects to the generated public route with `_preview=1`

`useCmsPreview()` returns `{ isPreview, previewToken, exitPreview }`. `useCmsPreviewToken()` is available for lower-level integrations.

## Revalidate

The revalidate endpoint is enabled unless `revalidate.enabled` is `false`. It expects a raw JSON CMS webhook payload signed with `@vigilkids/cms-client/webhook` HMAC verification.

```http
POST /api/_revalidate
X-Webhook-Signature: sha256=...
Content-Type: application/json
```

```json
{
  "event": "article.published",
  "resource_type": "article",
  "locale": "en",
  "slug": "getting-started",
  "category_slug": "guides",
  "route_params": {
    "category_slug": "guides",
    "slug": "getting-started"
  }
}
```

The endpoint builds index, category, and resource paths with the configured route policy, then removes matching Nitro route cache entries.

## Sitemap

There are two sitemap integration surfaces:

- `GET sitemap.sourcePath?locale=en` returns route-policy-generated sitemap entries for a single locale.
- `sitemap.enabled: true` hooks into `@nuxtjs/sitemap` and pushes CMS entries for `sitemap.locales`.

CMS sitemap entries can include resource types, route params, hreflang alternatives, images, `last_mod`, `change_freq`, and `priority`. The package converts them into Nuxt sitemap entries with public paths generated from the route policy.

## Composables

All runtime composables are auto-imported.

| Composable | Purpose |
| --- | --- |
| `useCmsClient()` | Returns a `CmsClient` configured from public CMS runtime config and preview token state. |
| `useCmsFetch<T>(path, options)` | SSR fetches CMS directly and CSR fetches through `content.cmsProxyPath`. |
| `useArticle(slug)` | Fetches article detail, related articles, SEO meta, Open Graph meta, Twitter meta, and JSON-LD. |
| `useArticleList(params)` | Fetches article lists through `CmsClient.getArticles`. |
| `useCmsArticleCollection(params)` | Fetches the normalized article collection endpoint with category, search, and pagination support. |
| `useCategories()` | Fetches categories through `CmsClient.getCategories`. |
| `useCmsCategories()` | Reads preloaded category data and exposes category lookup/path helpers. |
| `useAuthor(slug)` | Fetches author detail and author articles. |
| `useCmsPreviewToken()` | Reads the preview token in SSR and CSR contexts. |
| `useCmsPreview()` | Exposes preview state and exit behavior. |
| `useCmsRoutes()` | Exposes the resolved route policy and route builder. |
| `useBlogSeo(options)` | Applies list-page SEO meta. |
| `useHreflangLinks(resource, builder)` | Injects alternate links from resource locales with caller-defined URL building. |

## Compatibility

- Nuxt `>=3.15.0`
- `@vigilkids/cms-client ^0.3.1`
- Vue runtime supplied by Nuxt
- Node.js version should satisfy the consuming Nuxt application's runtime requirements

## License

[MIT](./LICENSE)
