@vigilkids/cms-nuxt 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/README.md +239 -22
- package/dist/module.d.mts +37 -2
- package/dist/module.d.ts +5 -44
- package/dist/module.d.ts.map +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +91 -5
- package/dist/runtime/composables/useArticle.d.ts +16 -0
- package/dist/runtime/composables/useArticle.d.ts.map +1 -0
- package/dist/runtime/composables/useArticleList.d.ts +11 -0
- package/dist/runtime/composables/useArticleList.d.ts.map +1 -0
- package/dist/runtime/composables/useAuthor.d.ts +10 -0
- package/dist/runtime/composables/useAuthor.d.ts.map +1 -0
- package/dist/runtime/composables/useBlogSeo.d.ts +25 -0
- package/dist/runtime/composables/useBlogSeo.d.ts.map +1 -0
- package/dist/runtime/composables/useBlogSeo.js +99 -13
- package/dist/runtime/composables/useCategories.d.ts +10 -0
- package/dist/runtime/composables/useCategories.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsArticleCollection.d.ts +14 -0
- package/dist/runtime/composables/useCmsArticleCollection.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsArticleCollection.js +16 -0
- package/dist/runtime/composables/useCmsCategories.d.ts +188 -0
- package/dist/runtime/composables/useCmsCategories.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsCategories.js +71 -0
- package/dist/runtime/composables/useCmsClient.d.ts +4 -0
- package/dist/runtime/composables/useCmsClient.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsFetch.d.ts +19 -0
- package/dist/runtime/composables/useCmsFetch.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsFetch.js +20 -0
- package/dist/runtime/composables/useCmsPreview.d.ts +16 -0
- package/dist/runtime/composables/useCmsPreview.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsRoutes.d.ts +7 -0
- package/dist/runtime/composables/useCmsRoutes.d.ts.map +1 -0
- package/dist/runtime/composables/useCmsRoutes.js +10 -0
- package/dist/runtime/composables/useHreflangLinks.d.ts +34 -0
- package/dist/runtime/composables/useHreflangLinks.d.ts.map +1 -0
- package/dist/runtime/composables/useHreflangLinks.js +2 -1
- package/dist/runtime/plugins/preview-init.client.d.ts +4 -0
- package/dist/runtime/plugins/preview-init.client.d.ts.map +1 -0
- package/dist/runtime/server/api/_revalidate.post.d.ts +9 -0
- package/dist/runtime/server/api/_revalidate.post.d.ts.map +1 -0
- package/dist/runtime/server/api/_revalidate.post.js +30 -10
- package/dist/runtime/server/api/articles.get.d.ts +4 -0
- package/dist/runtime/server/api/articles.get.d.ts.map +1 -0
- package/dist/runtime/server/api/articles.get.js +92 -0
- package/dist/runtime/server/api/preview.get.d.ts +4 -0
- package/dist/runtime/server/api/preview.get.d.ts.map +1 -0
- package/dist/runtime/server/api/preview.get.js +47 -12
- package/dist/runtime/server/api/sitemap-urls.get.d.ts +16 -0
- package/dist/runtime/server/api/sitemap-urls.get.d.ts.map +1 -0
- package/dist/runtime/server/api/sitemap-urls.get.js +20 -0
- package/dist/runtime/server/middleware/preview-context.d.ts +14 -0
- package/dist/runtime/server/middleware/preview-context.d.ts.map +1 -0
- package/dist/runtime/server/middleware/redirects.d.ts +4 -0
- package/dist/runtime/server/middleware/redirects.d.ts.map +1 -0
- package/dist/runtime/server/middleware/redirects.js +27 -10
- package/dist/runtime/server/routes/cms-proxy.d.ts +3 -0
- package/dist/runtime/server/routes/cms-proxy.d.ts.map +1 -0
- package/dist/runtime/server/routes/cms-proxy.js +55 -0
- package/dist/runtime/server/utils/cms-client.d.ts +4 -0
- package/dist/runtime/server/utils/cms-client.d.ts.map +1 -0
- package/dist/runtime/server/utils/cms-client.js +14 -7
- package/dist/runtime/sitemap/provider.d.ts +17 -0
- package/dist/runtime/sitemap/provider.d.ts.map +1 -0
- package/dist/runtime/sitemap/provider.js +9 -11
- package/dist/runtime/utils/list.d.ts +2 -0
- package/dist/runtime/utils/list.d.ts.map +1 -0
- package/dist/runtime/utils/routes.d.ts +19 -0
- package/dist/runtime/utils/routes.d.ts.map +1 -0
- package/dist/runtime/utils/routes.js +119 -0
- package/dist/runtime/utils/sitemap.d.ts +20 -0
- package/dist/runtime/utils/sitemap.d.ts.map +1 -0
- package/dist/runtime/utils/sitemap.js +44 -0
- package/dist/types.d.ts +76 -7
- package/dist/types.d.ts.map +1 -0
- package/package.json +7 -6
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.5.0] - 2026-06-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added `useLocalizedResourceSeo()` for locale-aware CMS resource metadata, canonical links, Open Graph tags, and Twitter Card tags.
|
|
13
|
+
- Added category metadata exposure in `useCmsCategories()` for taxonomy landing pages.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `useBlogSeo()` now delegates to the shared localized resource SEO pipeline.
|
|
18
|
+
- Updated peer dependency to `@vigilkids/cms-client ^0.3.0`.
|
|
19
|
+
|
|
20
|
+
## [0.4.0] - 2026-06-03
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Product-agnostic Resource Route Policy through `routes.resources`, `routes.sourceResources`, and `routes.locale`, allowing each consuming product to define public CMS resource paths without changing package runtime code.
|
|
25
|
+
- Configurable CMS content integration endpoints: article collection path, CMS fetch proxy path, preview path, revalidate path, and sitemap source path.
|
|
26
|
+
- `useCmsRoutes()` composable for route-policy access and resource path generation.
|
|
27
|
+
- `useCmsArticleCollection()` composable backed by the module-owned normalized article collection endpoint.
|
|
28
|
+
- `useCmsCategories()` composable for preloaded category data, category lookup, and route-policy category path generation.
|
|
29
|
+
- Generic preview route resolution for resource types and route params, including source route parsing and route-policy public redirects.
|
|
30
|
+
- Route-policy-aware sitemap source endpoint and optional `@nuxtjs/sitemap` provider integration.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Module setup now uses the `cms` module options as the source of truth and derives server/private and public runtime config from those options.
|
|
35
|
+
- Preview, revalidate, and sitemap URL generation now build paths from Resource Route Policy instead of a fixed article URL shape.
|
|
36
|
+
- Client-side CMS fetches now use the module CMS proxy while SSR fetches continue to call the CMS API directly.
|
|
37
|
+
- Dynamic CMS endpoints are registered with no-cache Nitro route rules.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
- Package-owned hardcoded public article route assumptions from preview, revalidate, and sitemap path generation.
|
|
42
|
+
- Direct consumer setup guidance that required writing CMS runtime config keys manually.
|
|
43
|
+
|
|
44
|
+
## [0.3.0] - 2026-05-08
|
|
45
|
+
|
|
46
|
+
### Breaking Changes
|
|
47
|
+
|
|
48
|
+
- `useArticle` no longer auto-injects hreflang `<link>` tags. The previous implementation hardcoded a `/blog/{slug}` URL pattern, which silently produced incorrect `href` values for any consumer whose article routes are not exactly `/{locale}/blog/{slug}` (e.g. category-based routes like `/{locale}/{category}/{slug}/`). Hreflang URL pattern is consumer-domain knowledge and must be supplied explicitly.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- `useHreflangLinks(resource, builder)` composable — generic hreflang `<link>` injector that filters by the resource's `available_locales` and adds an `x-default` entry. Works for any CMS resource with `slug`, `available_locales`, and `localized_slugs` fields (articles, authors, future series, etc.). The `builder` callback decides URL paths, locale prefix strategy, trailing slash, and origin, so it adapts to any route structure without forking the package.
|
|
53
|
+
|
|
54
|
+
### Migration
|
|
55
|
+
|
|
56
|
+
If you previously relied on `useArticle`'s auto hreflang:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// 0.2.x — implicit, hardcoded /blog/{slug}
|
|
60
|
+
const { article } = useArticle(slug)
|
|
61
|
+
|
|
62
|
+
// 0.3.0 — explicit, consumer-defined URL pattern
|
|
63
|
+
const { article } = useArticle(slug)
|
|
64
|
+
useHreflangLinks(article, ({ locale, slug, resource }) => {
|
|
65
|
+
const origin = `https://${siteUrl}`
|
|
66
|
+
if (locale === 'x-default') return `${origin}/blog/${slug}`
|
|
67
|
+
return `${origin}/${locale}/blog/${slug}`
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Consumers using non-`/blog/` routes (e.g. marketing site `/{category}/{slug}/`) were silently outputting broken hreflang under 0.2.x and should migrate immediately.
|
|
72
|
+
|
|
73
|
+
## [0.2.1] - 2026-03-24
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- Preview pages showing stale content after edits — Nitro SWR cache key now includes a unique timestamp for preview requests, ensuring each refresh bypasses the route-level SWR cache and renders fresh draft content
|
|
78
|
+
|
|
79
|
+
## [0.2.0] - 2026-03-24
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- `useCmsPreviewToken()` composable — SWR-safe preview token reading (SSR reads from `event.context`, CSR reads from cookie)
|
|
84
|
+
- `preview-context` server middleware — copies preview cookie to `event.context` so it survives Nitro SWR synthetic events
|
|
85
|
+
- Preview redirect now appends `?_preview=1` for separate SWR cache key isolation (prevents draft content leaking to public cache)
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- Preview 404 when Nitro SWR caching is enabled — SWR synthetic events strip all request headers including cookies, causing preview token loss during SSR rendering
|
|
90
|
+
- ISR revalidation cache key mismatch — `_revalidate.post.ts` now uses Nitro's internal key format (`escapeKey + hash`) with prefix matching instead of naive path-to-key conversion
|
|
91
|
+
|
|
92
|
+
## [0.1.0] - 2026-03-23
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
- ESLint flat config (`@antfu/eslint-config` + Vue) + Prettier
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- Codebase formatted with Prettier (singleQuote, no semi, printWidth 100)
|
|
101
|
+
|
|
102
|
+
## [0.0.1] - 2025-03-23
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- Nuxt 3 module for CMS integration (`@vigilkids/cms-nuxt`)
|
|
107
|
+
- Auto-registered composables: `useCmsArticle`, `useCmsPreview`, `useCmsSitemap`
|
|
108
|
+
- Server API route for preview token validation with secure cookie
|
|
109
|
+
- Preview mode middleware with cookie-based authentication
|
|
110
|
+
- Sitemap provider integration with `@nuxtjs/sitemap`
|
|
111
|
+
- Runtime config integration for CMS API base URL
|
package/README.md
CHANGED
|
@@ -1,56 +1,273 @@
|
|
|
1
1
|
# @vigilkids/cms-nuxt
|
|
2
2
|
|
|
3
|
-
Nuxt
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
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.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
pnpm add @vigilkids/cms-nuxt
|
|
10
|
+
pnpm add @vigilkids/cms-nuxt @vigilkids/cms-client
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
- `@
|
|
13
|
+
## Peer Dependencies
|
|
14
|
+
|
|
15
|
+
- `nuxt >=3.15.0`
|
|
16
|
+
- `@vigilkids/cms-client ^0.3.0`
|
|
17
|
+
- `@nuxtjs/sitemap` is optional and only required when `cms.sitemap.enabled` is `true`
|
|
15
18
|
|
|
16
|
-
## Setup
|
|
19
|
+
## Module Setup
|
|
20
|
+
|
|
21
|
+
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.
|
|
17
22
|
|
|
18
23
|
```typescript
|
|
19
|
-
// nuxt.config.ts
|
|
20
24
|
export default defineNuxtConfig({
|
|
21
25
|
modules: ['@vigilkids/cms-nuxt'],
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
cms: {
|
|
28
|
+
apiUrl: process.env.NUXT_CMS_API_URL ?? '',
|
|
29
|
+
productCode: process.env.NUXT_CMS_PRODUCT_CODE ?? '',
|
|
30
|
+
defaultLocale: 'en',
|
|
31
|
+
webhookSecret: process.env.NUXT_CMS_WEBHOOK_SECRET ?? '',
|
|
32
|
+
|
|
33
|
+
routes: {
|
|
34
|
+
resources: {
|
|
35
|
+
index: '/resources',
|
|
36
|
+
category: '/resources/{category_slug}',
|
|
37
|
+
article: '/resources/{category_slug}/{slug}',
|
|
38
|
+
author: '/resources/authors/{slug}',
|
|
39
|
+
},
|
|
40
|
+
sourceResources: {
|
|
41
|
+
index: '/',
|
|
42
|
+
category: '/{category_slug}',
|
|
43
|
+
article: '/{category_slug}/{slug}',
|
|
44
|
+
author: '/authors/{slug}',
|
|
45
|
+
},
|
|
46
|
+
locale: {
|
|
47
|
+
defaultLocale: 'en',
|
|
48
|
+
prefixes: {
|
|
49
|
+
'zh-CN': 'zh',
|
|
50
|
+
},
|
|
51
|
+
includeDefaultLocale: false,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
localeMap: {
|
|
56
|
+
'zh-CN': 'zh',
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
content: {
|
|
60
|
+
articleCollectionPath: '/api/cms/articles',
|
|
61
|
+
cmsProxyPath: '/cms-proxy',
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
categories: {
|
|
65
|
+
preload: true,
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
preview: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
path: '/api/preview',
|
|
71
|
+
cookieName: '__preview_token',
|
|
72
|
+
cookieMaxAge: 1800,
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
revalidate: {
|
|
76
|
+
enabled: true,
|
|
77
|
+
path: '/api/_revalidate',
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
sitemap: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
locales: ['en', 'zh-CN'],
|
|
83
|
+
sourcePath: '/api/cms/sitemap-urls',
|
|
84
|
+
},
|
|
25
85
|
},
|
|
26
86
|
})
|
|
27
87
|
```
|
|
28
88
|
|
|
29
|
-
##
|
|
89
|
+
## Module Options
|
|
90
|
+
|
|
91
|
+
| Option | Default | Purpose |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `apiUrl` | `''` | CMS API base URL used by `CmsClient`, server routes, and SSR fetches. |
|
|
94
|
+
| `productCode` | `''` | Sent as `X-Product-Code` when present. |
|
|
95
|
+
| `defaultLocale` | `'en'` | Public default locale used by composables and route policy. |
|
|
96
|
+
| `webhookSecret` | `''` | Server-only HMAC secret for the revalidate endpoint. |
|
|
97
|
+
| `routes.resources` | built-in defaults | Public resource URL patterns generated by `useCmsRoutes`, preview, revalidate, and sitemap. |
|
|
98
|
+
| `routes.sourceResources` | built-in defaults | CMS API resource URL patterns used to parse preview and sitemap input before rebuilding public URLs. |
|
|
99
|
+
| `routes.locale` | default locale policy | Locale prefix policy for generated public URLs. |
|
|
100
|
+
| `content.articleCollectionPath` | `'/api/cms/articles'` | Nuxt server endpoint for normalized article collection queries. |
|
|
101
|
+
| `content.cmsProxyPath` | `'/cms-proxy'` | Nuxt server proxy prefix used by client-side CMS fetches. |
|
|
102
|
+
| `categories.preload` | `false` | Fetches `/categories` during module setup and exposes the result in public runtime config. |
|
|
103
|
+
| `preview` | enabled by default | Preview route and preview cookie behavior. |
|
|
104
|
+
| `revalidate` | enabled by default | Signed ISR cache invalidation endpoint. |
|
|
105
|
+
| `redirects` | enabled by default | CMS redirect middleware with configurable in-memory cache TTL. |
|
|
106
|
+
| `sitemap` | disabled by default | `@nuxtjs/sitemap` provider integration and sitemap source endpoint path. |
|
|
107
|
+
| `localeMap` | `{}` | Maps public locale codes to CMS API locale codes. |
|
|
108
|
+
| `localePrefixes` | `{}` | Additional prefix input merged into `routes.locale.prefixes`. |
|
|
109
|
+
|
|
110
|
+
## Resource Route Policy
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
114
|
+
Default resource patterns are:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const defaultResourcePatterns = {
|
|
118
|
+
index: '/',
|
|
119
|
+
category: '/{category_slug}',
|
|
120
|
+
article: '/{category_slug}/{slug}',
|
|
121
|
+
author: '/authors/{slug}',
|
|
122
|
+
} as const
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`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:
|
|
30
126
|
|
|
31
127
|
```typescript
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
128
|
+
export default defineNuxtConfig({
|
|
129
|
+
cms: {
|
|
130
|
+
routes: {
|
|
131
|
+
resources: {
|
|
132
|
+
article: '/learn/{category_slug}/{slug}',
|
|
133
|
+
},
|
|
134
|
+
sourceResources: {
|
|
135
|
+
article: '/{category_slug}/{slug}',
|
|
136
|
+
},
|
|
137
|
+
locale: {
|
|
138
|
+
defaultLocale: 'en',
|
|
139
|
+
prefixes: { fr: 'fr' },
|
|
140
|
+
includeDefaultLocale: false,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
})
|
|
35
145
|
```
|
|
36
146
|
|
|
37
|
-
|
|
147
|
+
`useCmsRoutes()` exposes the resolved policy and route builder:
|
|
38
148
|
|
|
39
|
-
|
|
149
|
+
```vue
|
|
150
|
+
<script setup lang="ts">
|
|
151
|
+
const cmsRoutes = useCmsRoutes()
|
|
40
152
|
|
|
153
|
+
const articlePath = cmsRoutes.build('article', {
|
|
154
|
+
category_slug: 'guides',
|
|
155
|
+
slug: 'getting-started',
|
|
156
|
+
locale: 'fr',
|
|
157
|
+
}, true)
|
|
158
|
+
</script>
|
|
41
159
|
```
|
|
42
|
-
|
|
43
|
-
|
|
160
|
+
|
|
161
|
+
A new product should change only:
|
|
162
|
+
|
|
163
|
+
- `cms.routes.resources`, `cms.routes.sourceResources`, and `cms.routes.locale`
|
|
164
|
+
- Nuxt page shells that map those URL patterns to UI components
|
|
165
|
+
|
|
166
|
+
The CMS client, preview endpoint, revalidation endpoint, category preload, fetch proxy, and sitemap source stay inside the package.
|
|
167
|
+
|
|
168
|
+
## Content Endpoints
|
|
169
|
+
|
|
170
|
+
The module registers no-cache Nuxt server endpoints from the configured paths.
|
|
171
|
+
|
|
172
|
+
| Endpoint | Method | Behavior |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| `content.articleCollectionPath` | `GET` | Returns `Paginated<ArticleListItem>` with `locale`, `category_slug`, `q`, `page`, and `page_size` query support. Search results are normalized into article cards. |
|
|
175
|
+
| `${content.cmsProxyPath}/**` | any | Proxies client-side CMS requests to `apiUrl`, forwarding `X-Product-Code` and preview token headers when present. |
|
|
176
|
+
| `sitemap.sourcePath` | `GET` | Returns Nuxt sitemap entries for `?locale=...` using route policy and CMS sitemap data. |
|
|
177
|
+
| `preview.path` | `GET` | Reads preview input, sets the preview cookie, and redirects to the route-policy-generated resource path. |
|
|
178
|
+
| `revalidate.path` | `POST` | Verifies `x-webhook-signature` and clears matching Nitro route cache entries. |
|
|
179
|
+
|
|
180
|
+
## Categories
|
|
181
|
+
|
|
182
|
+
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()`.
|
|
183
|
+
|
|
184
|
+
```vue
|
|
185
|
+
<script setup lang="ts">
|
|
186
|
+
const { currentCategories, getCategoryPath, categoryExists } = useCmsCategories()
|
|
187
|
+
</script>
|
|
44
188
|
```
|
|
45
189
|
|
|
190
|
+
Use `useCategories()` when the page should fetch categories through `@vigilkids/cms-client` with `useAsyncData`.
|
|
191
|
+
|
|
192
|
+
## Preview
|
|
193
|
+
|
|
194
|
+
Preview mode is enabled unless `preview.enabled` is `false`.
|
|
195
|
+
|
|
196
|
+
```http
|
|
197
|
+
GET /api/preview?token=preview-token&resource=article&slug=guides/getting-started&locale=en
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The preview endpoint:
|
|
201
|
+
|
|
202
|
+
- reads `token`, `resource` or `resource_type`, `slug`, `locale`, and additional route params from the query string
|
|
203
|
+
- parses `slug` with `routes.sourceResources` when possible
|
|
204
|
+
- fetches article detail when the public article pattern requires `category_slug`
|
|
205
|
+
- sets the configured preview cookie
|
|
206
|
+
- redirects to the generated public route with `_preview=1`
|
|
207
|
+
|
|
208
|
+
`useCmsPreview()` returns `{ isPreview, previewToken, exitPreview }`. `useCmsPreviewToken()` is available for lower-level integrations.
|
|
209
|
+
|
|
210
|
+
## Revalidate
|
|
211
|
+
|
|
212
|
+
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.
|
|
213
|
+
|
|
214
|
+
```http
|
|
215
|
+
POST /api/_revalidate
|
|
216
|
+
X-Webhook-Signature: sha256=...
|
|
217
|
+
Content-Type: application/json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"event": "article.published",
|
|
223
|
+
"resource_type": "article",
|
|
224
|
+
"locale": "en",
|
|
225
|
+
"slug": "getting-started",
|
|
226
|
+
"category_slug": "guides",
|
|
227
|
+
"route_params": {
|
|
228
|
+
"category_slug": "guides",
|
|
229
|
+
"slug": "getting-started"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The endpoint builds index, category, and resource paths with the configured route policy, then removes matching Nitro route cache entries.
|
|
235
|
+
|
|
46
236
|
## Sitemap
|
|
47
237
|
|
|
48
|
-
|
|
238
|
+
There are two sitemap integration surfaces:
|
|
239
|
+
|
|
240
|
+
- `GET sitemap.sourcePath?locale=en` returns route-policy-generated sitemap entries for a single locale.
|
|
241
|
+
- `sitemap.enabled: true` hooks into `@nuxtjs/sitemap` and pushes CMS entries for `sitemap.locales`.
|
|
242
|
+
|
|
243
|
+
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.
|
|
244
|
+
|
|
245
|
+
## Composables
|
|
246
|
+
|
|
247
|
+
All runtime composables are auto-imported.
|
|
248
|
+
|
|
249
|
+
| Composable | Purpose |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `useCmsClient()` | Returns a `CmsClient` configured from public CMS runtime config and preview token state. |
|
|
252
|
+
| `useCmsFetch<T>(path, options)` | SSR fetches CMS directly and CSR fetches through `content.cmsProxyPath`. |
|
|
253
|
+
| `useArticle(slug)` | Fetches article detail, related articles, SEO meta, Open Graph meta, Twitter meta, and JSON-LD. |
|
|
254
|
+
| `useArticleList(params)` | Fetches article lists through `CmsClient.getArticles`. |
|
|
255
|
+
| `useCmsArticleCollection(params)` | Fetches the normalized article collection endpoint with category, search, and pagination support. |
|
|
256
|
+
| `useCategories()` | Fetches categories through `CmsClient.getCategories`. |
|
|
257
|
+
| `useCmsCategories()` | Reads preloaded category data and exposes category lookup/path helpers. |
|
|
258
|
+
| `useAuthor(slug)` | Fetches author detail and author articles. |
|
|
259
|
+
| `useCmsPreviewToken()` | Reads the preview token in SSR and CSR contexts. |
|
|
260
|
+
| `useCmsPreview()` | Exposes preview state and exit behavior. |
|
|
261
|
+
| `useCmsRoutes()` | Exposes the resolved route policy and route builder. |
|
|
262
|
+
| `useBlogSeo(options)` | Applies list-page SEO meta. |
|
|
263
|
+
| `useHreflangLinks(resource, builder)` | Injects alternate links from resource locales with caller-defined URL building. |
|
|
49
264
|
|
|
50
265
|
## Compatibility
|
|
51
266
|
|
|
52
|
-
- Nuxt
|
|
53
|
-
-
|
|
267
|
+
- Nuxt `>=3.15.0`
|
|
268
|
+
- `@vigilkids/cms-client ^0.3.0`
|
|
269
|
+
- Vue runtime supplied by Nuxt
|
|
270
|
+
- Node.js version should satisfy the consuming Nuxt application's runtime requirements
|
|
54
271
|
|
|
55
272
|
## License
|
|
56
273
|
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import { NuxtModule } from '@nuxt/schema';
|
|
2
2
|
|
|
3
|
+
/** CMS 资源路由模式 */
|
|
4
|
+
interface CmsRouteResources {
|
|
5
|
+
index?: string;
|
|
6
|
+
category?: string;
|
|
7
|
+
article?: string;
|
|
8
|
+
author?: string;
|
|
9
|
+
[resource: string]: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/** CMS 语言路径策略 */
|
|
12
|
+
interface CmsRouteLocalePolicy {
|
|
13
|
+
defaultLocale?: string;
|
|
14
|
+
prefixes?: Record<string, string>;
|
|
15
|
+
includeDefaultLocale?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** CMS 资源路由策略 */
|
|
18
|
+
interface CmsRoutePolicy {
|
|
19
|
+
/** 站点公开 URL 模式 */
|
|
20
|
+
resources?: CmsRouteResources;
|
|
21
|
+
/** CMS API 返回的资源 URL 模式,用于 preview/sitemap 输入解析 */
|
|
22
|
+
sourceResources?: CmsRouteResources;
|
|
23
|
+
/** 语言路径策略 */
|
|
24
|
+
locale?: CmsRouteLocalePolicy;
|
|
25
|
+
}
|
|
3
26
|
/** CMS Nuxt Module 配置 */
|
|
4
27
|
interface CmsNuxtModuleOptions {
|
|
5
28
|
/** CMS API 基础 URL */
|
|
@@ -13,10 +36,21 @@ interface CmsNuxtModuleOptions {
|
|
|
13
36
|
/** 预览模式配置 */
|
|
14
37
|
preview?: {
|
|
15
38
|
enabled?: boolean;
|
|
39
|
+
path?: string;
|
|
16
40
|
cookieName?: string;
|
|
17
41
|
cookieMaxAge?: number;
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
};
|
|
43
|
+
/** CMS 资源路由策略 */
|
|
44
|
+
routes?: CmsRoutePolicy;
|
|
45
|
+
/** 内容 API 集成 */
|
|
46
|
+
content?: {
|
|
47
|
+
articleCollectionPath?: string;
|
|
48
|
+
cmsProxyPath?: string;
|
|
49
|
+
};
|
|
50
|
+
/** 分类预加载配置 */
|
|
51
|
+
categories?: {
|
|
52
|
+
preload?: boolean;
|
|
53
|
+
locales?: string[];
|
|
20
54
|
};
|
|
21
55
|
/** ISR 重验证配置 */
|
|
22
56
|
revalidate?: {
|
|
@@ -32,6 +66,7 @@ interface CmsNuxtModuleOptions {
|
|
|
32
66
|
sitemap?: {
|
|
33
67
|
enabled?: boolean;
|
|
34
68
|
locales?: string[];
|
|
69
|
+
sourcePath?: string;
|
|
35
70
|
};
|
|
36
71
|
/** 语言代码映射 (前端 locale → CMS API locale) */
|
|
37
72
|
localeMap?: Record<string, string>;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
|
-
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
-
|
|
3
|
-
/** CMS Nuxt Module 配置 */
|
|
4
|
-
interface CmsNuxtModuleOptions {
|
|
5
|
-
/** CMS API 基础 URL */
|
|
6
|
-
apiUrl: string;
|
|
7
|
-
/** 产品编码 */
|
|
8
|
-
productCode: string;
|
|
9
|
-
/** 默认语言 */
|
|
10
|
-
defaultLocale?: string;
|
|
11
|
-
/** Webhook HMAC-SHA256 密钥(服务端专用) */
|
|
12
|
-
webhookSecret: string;
|
|
13
|
-
/** 预览模式配置 */
|
|
14
|
-
preview?: {
|
|
15
|
-
enabled?: boolean;
|
|
16
|
-
cookieName?: string;
|
|
17
|
-
cookieMaxAge?: number;
|
|
18
|
-
/** 文章路径模式,slug 会替换 {slug} 占位符。默认 '/blog/{slug}' */
|
|
19
|
-
articlePath?: string;
|
|
20
|
-
};
|
|
21
|
-
/** ISR 重验证配置 */
|
|
22
|
-
revalidate?: {
|
|
23
|
-
enabled?: boolean;
|
|
24
|
-
path?: string;
|
|
25
|
-
};
|
|
26
|
-
/** 重定向中间件配置 */
|
|
27
|
-
redirects?: {
|
|
28
|
-
enabled?: boolean;
|
|
29
|
-
cacheTtl?: number;
|
|
30
|
-
};
|
|
31
|
-
/** Sitemap 集成 */
|
|
32
|
-
sitemap?: {
|
|
33
|
-
enabled?: boolean;
|
|
34
|
-
locales?: string[];
|
|
35
|
-
};
|
|
36
|
-
/** 语言代码映射 (前端 locale → CMS API locale) */
|
|
37
|
-
localeMap?: Record<string, string>;
|
|
38
|
-
/** URL 路径前缀映射 (CMS locale → URL prefix),默认语言无需配置 */
|
|
39
|
-
localePrefixes?: Record<string, string>;
|
|
40
|
-
}
|
|
41
|
-
|
|
1
|
+
import type { NuxtModule } from '@nuxt/schema';
|
|
2
|
+
import type { CmsNuxtModuleOptions } from './types';
|
|
42
3
|
declare const module: NuxtModule<CmsNuxtModuleOptions>;
|
|
43
|
-
|
|
44
|
-
export {
|
|
45
|
-
|
|
4
|
+
export default module;
|
|
5
|
+
export type { CmsNuxtModuleOptions } from './types';
|
|
6
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,UAAU,EAAE,MAAM,cAAc,CAAA;AAEpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAiEnD,QAAA,MAAM,MAAM,EAAE,UAAU,CAAC,oBAAoB,CAoJ3C,CAAA;AAEF,eAAe,MAAM,CAAA;AAGrB,YAAY,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addImportsDir, addServerImportsDir, addServerHandler, addPlugin } from '@nuxt/kit';
|
|
2
2
|
|
|
3
|
+
function emptyCategoriesData() {
|
|
4
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5
|
+
return { lastUpdated: now, buildTime: now, items: [] };
|
|
6
|
+
}
|
|
7
|
+
async function preloadCategoriesData(options) {
|
|
8
|
+
if (!options.apiUrl)
|
|
9
|
+
return emptyCategoriesData();
|
|
10
|
+
try {
|
|
11
|
+
const url = `${options.apiUrl.replace(/\/+$/g, "")}/categories`;
|
|
12
|
+
const headers = { Accept: "application/json" };
|
|
13
|
+
if (options.productCode)
|
|
14
|
+
headers["X-Product-Code"] = options.productCode;
|
|
15
|
+
const response = await fetch(url, { headers });
|
|
16
|
+
if (!response.ok)
|
|
17
|
+
return emptyCategoriesData();
|
|
18
|
+
const payload = await response.json();
|
|
19
|
+
const categories = Array.isArray(payload) ? payload : payload.data;
|
|
20
|
+
return {
|
|
21
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22
|
+
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23
|
+
items: categories.filter((category) => category.slug && category.name)
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return emptyCategoriesData();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function applyNoCacheRouteRules(nuxt, paths) {
|
|
30
|
+
const options = nuxt.options;
|
|
31
|
+
options.nitro = options.nitro ?? {};
|
|
32
|
+
options.nitro.routeRules = options.nitro.routeRules ?? {};
|
|
33
|
+
for (const path of paths) {
|
|
34
|
+
options.nitro.routeRules[path] = {
|
|
35
|
+
swr: false,
|
|
36
|
+
cache: false,
|
|
37
|
+
headers: { "cache-control": "private, no-cache, no-store" }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
3
41
|
const module = defineNuxtModule({
|
|
4
42
|
meta: {
|
|
5
43
|
name: "@vigilkids/cms-nuxt",
|
|
@@ -12,14 +50,30 @@ const module = defineNuxtModule({
|
|
|
12
50
|
defaultLocale: "en",
|
|
13
51
|
webhookSecret: "",
|
|
14
52
|
preview: { cookieName: "__preview_token", cookieMaxAge: 1800 },
|
|
53
|
+
content: { articleCollectionPath: "/api/cms/articles", cmsProxyPath: "/cms-proxy" },
|
|
54
|
+
categories: { preload: false },
|
|
15
55
|
revalidate: { enabled: true, path: "/api/_revalidate" },
|
|
16
56
|
redirects: { enabled: true, cacheTtl: 3e5 },
|
|
17
57
|
sitemap: { enabled: false },
|
|
18
58
|
localeMap: {},
|
|
19
59
|
localePrefixes: {}
|
|
20
60
|
},
|
|
21
|
-
setup(options, nuxt) {
|
|
61
|
+
async setup(options, nuxt) {
|
|
22
62
|
const { resolve } = createResolver(import.meta.url);
|
|
63
|
+
const categoriesData = options.categories?.preload ? await preloadCategoriesData(options) : emptyCategoriesData();
|
|
64
|
+
const articleCollectionPath = options.content?.articleCollectionPath ?? "/api/cms/articles";
|
|
65
|
+
const cmsProxyPath = options.content?.cmsProxyPath ?? "/cms-proxy";
|
|
66
|
+
const cmsProxyRoute = cmsProxyPath.endsWith("/**") ? cmsProxyPath : `${cmsProxyPath}/**`;
|
|
67
|
+
const previewPath = options.preview?.path ?? "/api/preview";
|
|
68
|
+
const revalidatePath = options.revalidate?.path ?? "/api/_revalidate";
|
|
69
|
+
const sitemapSourcePath = options.sitemap?.sourcePath ?? "/api/cms/sitemap-urls";
|
|
70
|
+
applyNoCacheRouteRules(nuxt, [
|
|
71
|
+
articleCollectionPath,
|
|
72
|
+
cmsProxyRoute,
|
|
73
|
+
sitemapSourcePath,
|
|
74
|
+
previewPath,
|
|
75
|
+
revalidatePath
|
|
76
|
+
]);
|
|
23
77
|
nuxt.options.runtimeConfig.cms = {
|
|
24
78
|
webhookSecret: options.webhookSecret,
|
|
25
79
|
redirects: {
|
|
@@ -33,8 +87,26 @@ const module = defineNuxtModule({
|
|
|
33
87
|
defaultLocale: options.defaultLocale ?? "en",
|
|
34
88
|
preview: {
|
|
35
89
|
cookieName: options.preview?.cookieName ?? "__preview_token",
|
|
36
|
-
cookieMaxAge: options.preview?.cookieMaxAge ?? 1800
|
|
37
|
-
|
|
90
|
+
cookieMaxAge: options.preview?.cookieMaxAge ?? 1800
|
|
91
|
+
},
|
|
92
|
+
routes: {
|
|
93
|
+
...options.routes ?? {},
|
|
94
|
+
locale: {
|
|
95
|
+
defaultLocale: options.routes?.locale?.defaultLocale ?? options.defaultLocale ?? "en",
|
|
96
|
+
prefixes: {
|
|
97
|
+
...options.localePrefixes ?? {},
|
|
98
|
+
...options.routes?.locale?.prefixes ?? {}
|
|
99
|
+
},
|
|
100
|
+
includeDefaultLocale: options.routes?.locale?.includeDefaultLocale ?? false
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
content: {
|
|
104
|
+
articleCollectionPath,
|
|
105
|
+
cmsProxyPath
|
|
106
|
+
},
|
|
107
|
+
categories: categoriesData,
|
|
108
|
+
sitemap: {
|
|
109
|
+
sourcePath: sitemapSourcePath
|
|
38
110
|
},
|
|
39
111
|
localeMap: options.localeMap ?? {},
|
|
40
112
|
localePrefixes: options.localePrefixes ?? {}
|
|
@@ -43,18 +115,32 @@ const module = defineNuxtModule({
|
|
|
43
115
|
addServerImportsDir(resolve("./runtime/server/utils"));
|
|
44
116
|
if (options.revalidate?.enabled !== false) {
|
|
45
117
|
addServerHandler({
|
|
46
|
-
route:
|
|
118
|
+
route: revalidatePath,
|
|
47
119
|
method: "post",
|
|
48
120
|
handler: resolve("./runtime/server/api/_revalidate.post")
|
|
49
121
|
});
|
|
50
122
|
}
|
|
123
|
+
addServerHandler({
|
|
124
|
+
route: articleCollectionPath,
|
|
125
|
+
method: "get",
|
|
126
|
+
handler: resolve("./runtime/server/api/articles.get")
|
|
127
|
+
});
|
|
128
|
+
addServerHandler({
|
|
129
|
+
route: sitemapSourcePath,
|
|
130
|
+
method: "get",
|
|
131
|
+
handler: resolve("./runtime/server/api/sitemap-urls.get")
|
|
132
|
+
});
|
|
133
|
+
addServerHandler({
|
|
134
|
+
route: cmsProxyRoute,
|
|
135
|
+
handler: resolve("./runtime/server/routes/cms-proxy")
|
|
136
|
+
});
|
|
51
137
|
if (options.preview?.enabled !== false) {
|
|
52
138
|
addServerHandler({
|
|
53
139
|
middleware: true,
|
|
54
140
|
handler: resolve("./runtime/server/middleware/preview-context")
|
|
55
141
|
});
|
|
56
142
|
addServerHandler({
|
|
57
|
-
route:
|
|
143
|
+
route: previewPath,
|
|
58
144
|
method: "get",
|
|
59
145
|
handler: resolve("./runtime/server/api/preview.get")
|
|
60
146
|
});
|