@stackonward/cms-nuxt
v0.0.4
Published
Nuxt 3 CMS runtime with composables, server routes, preview, and sitemap support
Downloads
621
Readme
@stackonward/cms-nuxt
Nuxt module for product-agnostic CMS delivery integration. It registers CMS
runtime configuration, auto-imported composables, Nuxt server endpoints,
preview support, cache revalidation, runtime site configuration, 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
pnpm add @stackonward/cms-nuxt @stackonward/cms-client @stackonward/section-renderer-vuePeer Dependencies
nuxt >=3.15.0@stackonward/cms-client ^0.0.3@stackonward/section-renderer-vue ^0.0.1- install
@nuxtjs/sitemapwhencms.sitemap.enabledistrue
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.
export default defineNuxtConfig({
modules: ['@stackonward/cms-nuxt'],
cms: {
apiUrl: process.env.NUXT_CMS_API_URL ?? '',
upstreamHeaders: {
'X-Customer-Scope': process.env.NUXT_CMS_CUSTOMER_SCOPE ?? '',
},
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',
},
site: {
code: 'product-site',
runtime: true,
cacheTtl: 300000,
},
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',
},
},
})Backend-specific headers are composed outside the generic module. OneX consumers use the explicit adapter:
import { createOneXCmsUpstreamHeaders } from '@stackonward/onex-cms-client'
export default defineNuxtConfig({
cms: {
apiUrl: process.env.NUXT_CMS_API_URL ?? '',
upstreamHeaders: createOneXCmsUpstreamHeaders('product_alpha'),
webhookSecret: process.env.NUXT_CMS_WEBHOOK_SECRET ?? '',
},
})Module Options
| Option | Default | Purpose |
| ------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------- |
| apiUrl | '' | CMS API base URL used by CmsClient, server routes, and SSR fetches. |
| upstreamHeaders | {} | Server-only static headers sent to the CMS upstream. |
| 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. |
| site.code | '' | Stable CMS site lookup key used before URL or request-host lookup. |
| site.url | '' | Site URL whose host is used when no site code is configured. |
| site.runtime | false | Loads CMS site config and its route policy at request/runtime boundaries. |
| site.cacheTtl | 300000 | In-memory server cache TTL for resolved site config; 0 disables caching. |
| 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:
const defaultResourcePatterns = {
index: '/',
category: '/{category_slug}',
article: '/{category_slug}/{slug}',
author: '/authors/{slug}',
} as constroutes.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:
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:
<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, andcms.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, applying server-only upstream headers and the preview token when present. |
| /api/cms/site | GET | Resolves CMS site configuration by configured code, configured URL host, or request host. |
| 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().
<script setup lang="ts">
const { currentCategories, getCategoryPath, categoryExists } = useCmsCategories()
</script>Use useCategories() when the page should fetch categories through @stackonward/cms-client with useAsyncData.
Runtime site configuration
Set site.runtime: true when the CMS owns per-site route policy and site
metadata. Resolution prefers site.code, then the host from site.url, then
the current request host. The server rejects a site response without
route_policy instead of silently falling back to static routes.
The site plugin loads /api/cms/site once into useCmsSiteConfig() for SSR and
client hydration. Preview, redirect, sitemap, and server route builders use the
resolved CMS route policy while runtime mode is enabled. Keep site.runtime
disabled when the Nuxt configuration is the single route-policy owner.
Preview
Preview mode is enabled unless preview.enabled is false.
GET /api/preview?token=preview-token&resource=article&slug=guides/getting-started&locale=enThe preview endpoint:
- reads
token,resourceorresource_type,slug,locale, and additional route params from the query string - parses
slugwithroutes.sourceResourceswhen 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 @stackonward/cms-client/webhook HMAC verification.
POST /api/_revalidate
X-Webhook-Signature: sha256=...
Content-Type: application/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=enreturns route-policy-generated sitemap entries for a single locale.sitemap.enabled: truehooks into@nuxtjs/sitemapand pushes CMS entries forsitemap.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. |
| useCmsSiteConfig() | Reads the runtime CMS site configuration loaded during app setup. |
| useBlogSeo(options) | Applies list-page SEO meta. |
| useLocalizedResourceSeo(options) | Applies canonical, alternate, social, and structured SEO for a localized resource. |
| useHreflangLinks(resource, builder) | Injects alternate links from resource locales with caller-defined URL building. |
Compatibility
- Nuxt
>=3.15.0 @stackonward/cms-client ^0.0.3@stackonward/section-renderer-vue ^0.0.1- Vue runtime supplied by Nuxt
- Node.js version should satisfy the consuming Nuxt application's runtime requirements
