
Nuxt Contentstack
用于 Nuxt 的 Contentstack 集成。
注意:这是一个由 @timbenniks 维护的 OSS 项目,**不是**由 Contentstack 团队官方维护的包。支持请求可以通过 Github issues 和直接渠道联系 @timbenniks。
功能
- ⚡️ 轻松设置
- ⚡️ Contentstack 的完整 Vue 可组合项集合
- ⚡️ 通过高级筛选查询条目和资产
- ⚡️ 高级筛选和分页
- ⚡️ 带有响应式 URL 的图像转换
- ⚡️ @nuxt/image 集成,自动优化
- ⚡️ 实时预览和可视化构建器
- ⚡️ 个性化支持
- ⚡️ 带有完整类型安全的 TypeScript 支持
- ⚡️ 暴露的 SDK:TS Delivery SDK、Live Preview Utils SDK、Personalize SDK
快速设置
通过一个命令将模块安装到您的 Nuxt 应用中
npx nuxi module add nuxt-contentstack
或者:添加到 nuxt.config.ts
modules: ['nuxt-contentstack'],
'nuxt-contentstack': {
// Required core settings
apiKey: 'your_contentstack_api_key',
deliveryToken: 'your_delivery_token',
environment: 'your_environment',
// Optional settings with smart defaults
region: 'eu',
branch: 'main',
locale: 'en-us',
// Live Preview
livePreview: {
enable: true,
previewToken: 'your_preview_token', // no need for preview token if you are not using live preview
editableTags: true,
editButton: true
},
// Personalization
personalization: {
enable: true,
projectUid: 'your_project_uid'
},
debug: true
},
选项
核心设置(必填)
apiKey- 您的 Contentstack 堆栈 API 密钥(以“blt”开头)deliveryToken- 您的 Contentstack 交付令牌(以“cs”开头)environment- 目标环境(“preview”|“production”)
核心设置(可选)
region- Contentstack 区域:“us”|“eu”|“au”|“azure-na”|“azure-eu”|“gcp-na”|“gcp-eu”(默认值:“us”)branch- 内容分支(默认值:“main”)locale- 默认语言环境(默认值:“en-us”)
实时预览设置
配置 livePreview 对象,包含:
enable- 启用实时预览模式previewToken- Contentstack 的预览令牌(以“cs”开头,如果启用则为必填)editableTags- 添加可编辑标签以进行可视化构建(也控制自动 CSLP 属性清理)editButton- 启用编辑按钮(布尔值或详细配置对象)mode- 实时预览模式:“builder”|“preview”(默认值:“builder”)ssr- 启用 SSR 模式(实验性,默认值:false)
个性化设置
配置 personalization 对象,包含:
enable- 启用个性化功能projectUid- 您的个性化项目 UID(在 Contentstack UI 中找到)
通用设置
debug- 启用调试日志和配置转储
个性化示例
// get Personalize SDK
const { Personalize } = useNuxtApp().$contentstack;
// set attribute
await Personalize.set({ age: 20 });
// trigger impression
// experienceShortId to be found on the experiences list page in contentstack
experienceShortId = 0;
await Personalize.triggerImpression(experienceShortId);
// trigger conversion event
// 'eventKey' can be found when creatign an event in Contentstack Personalize
await Personalize.triggerEvent("eventKey");
提供
此模块提供一个 $contentstack 对象,包含:
stack:来自 Delivery SDK 的 Stack 对象。使用它查询所有内容。ContentstackLivePreview:Live Preview Utils SDK 的实例。livePreviewEnabled:是否启用了实时预览?editableTags:我们是否需要用于可视化构建的可编辑标签?Personalize:Personalize SDK 的实例。variantAlias:要传递给 Delivery SDK 的变体清单。
const {
editableTags,
stack,
livePreviewEnabled,
ContentstackLivePreview,
Personalize,
variantAlias,
VB_EmptyBlockParentClass,
} = useNuxtApp().$contentstack;
组件
此模块为常见的 Contentstack 用例提供了 Vue 组件。
ContentstackModularBlocks
一个灵活、通用的组件,用于将 Contentstack 模块块渲染为 Vue 组件。非常适合动态页面布局、组件库和内容驱动的 UI。
功能
- ✅ 自动组件映射 - 自动将 Contentstack 块类型映射到 Vue 组件
- ✅ 自动获取能力 - 可以自动获取条目数据并提取块(新功能)
- ✅ 灵活的数据结构 - 支持各种 Contentstack 模块块格式
- ✅ 实时预览就绪 - 全面支持 Contentstack 实时预览,带有
data-cslp属性 - ✅ 可视化构建器支持 - 包含用于可视化构建的空状态类
- ✅ TypeScript 支持 - 带有泛型的全面类型定义
- ✅ SSR 兼容 - 在服务器上完美渲染并无缝水合
- ✅ 可定制样式 - 可配置的 CSS 类和容器属性
- ✅ 错误处理 - 未映射组件的优雅回退
- ✅ 插槽支持 - 通过插槽实现自定义加载、错误和空状态内容
使用模式
该组件支持两种使用模式,以实现最大的灵活性
模式 1:自动获取条目 + 渲染块(新功能)
非常适合简单的页面渲染 - 只需提供条目详细信息,让组件处理所有事情
<script setup>
import Hero from "./components/Hero.vue";
import Grid from "./components/Grid.vue";
import TextBlock from "./components/TextBlock.vue";
// Map Contentstack block types to Vue components
const componentMapping = {
hero: Hero,
grid: Grid,
text_block: TextBlock,
};
</script>
<template>
<!-- Component fetches entry and renders blocks automatically -->
<ContentstackModularBlocks
content-type-uid="page"
:url="$route.path"
blocks-field-path="components"
:reference-field-path="['blocks.block.image']"
:json-rte-path="['rich_text', 'blocks.block.copy']"
locale="en-us"
:component-map="componentMapping"
>
<!-- Custom loading state -->
<template #loading>
<div class="loading-spinner">Loading page content...</div>
</template>
<!-- Custom error state -->
<template #error>
<div class="error-message">Failed to load content</div>
</template>
</ContentstackModularBlocks>
</template>
模式 2:传统模式,带预取块
当您需要对数据获取有更多控制时使用
<script setup>
import Hero from "./components/Hero.vue";
import Grid from "./components/Grid.vue";
import TextBlock from "./components/TextBlock.vue";
// Map Contentstack block types to Vue components
const componentMapping = {
hero: Hero,
grid: Grid,
text_block: TextBlock,
};
// Fetch your page data manually
const { data: page } = await useGetEntryByUrl({
contentTypeUid: "page",
url: useRoute().path,
});
</script>
<template>
<!-- Pass pre-fetched blocks -->
<ContentstackModularBlocks
:blocks="page.components"
:component-map="componentMapping"
/>
</template>
高级用法
<script setup>
import Hero from "./blocks/Hero.vue";
import Grid from "./blocks/Grid.vue";
import DefaultBlock from "./blocks/DefaultBlock.vue";
const componentMapping = {
hero: Hero,
grid: Grid,
text_section: TextSection,
image_gallery: ImageGallery,
};
</script>
<template>
<ContentstackModularBlocks
:blocks="page.modular_blocks"
:component-map="componentMapping"
:fallback-component="DefaultBlock"
:auto-extract-block-name="true"
:show-empty-state="true"
container-class="page-blocks"
empty-block-class="visual-builder__empty-block-parent"
empty-state-message="No content blocks available"
key-field="_metadata.uid"
block-name-prefix="block_"
:container-props="{ 'data-page-id': page.uid }"
>
<!-- Custom empty state -->
<template #empty>
<div class="custom-empty-state">
<h3>No content blocks found</h3>
<p>Please add some content in Contentstack</p>
</div>
</template>
</ContentstackModularBlocks>
</template>
属性
核心属性
| 属性 | 类型 | 默认 | 描述 |
|---|---|---|---|
blocks | ContentstackBlock[] | [] | Contentstack 模块块数组 |
componentMap | ComponentMapping | {} | 将块类型映射到 Vue 组件的对象 |
fallbackComponent | Component | string | ContentstackFallbackBlock | 未映射块类型的回退组件 |
自动获取属性(新功能)
| 属性 | 类型 | 默认 | 描述 |
|---|---|---|---|
contentTypeUid | string | 未定义 | 用于获取条目的内容类型 UID |
url | string | 未定义 | 通过 URL 获取条目 |
referenceFieldPath | string[] | [] | 要包含的引用字段路径 |
jsonRtePath | string[] | [] | JSON RTE 字段路径 |
locale | string | 'en-us' | 条目的语言环境 |
replaceHtmlCslp | boolean | false | 替换 HTML CSLP 标签 |
blocksFieldPath | string | 'components' | 从中提取模块块的字段路径 |
seoMeta | SeoMetaInput | 未定义 | SEO 元数据对象(直接传递给 useSeoMeta) |
autoSeoMeta | boolean | Record<string, string> | false | 从条目数据自动生成 SEO |
样式属性
| 属性 | 类型 | 默认 | 描述 |
|---|---|---|---|
containerClass | string | 'contentstack-modular-blocks' | 容器的 CSS 类 |
emptyBlockClass | string | 'visual-builder__empty-block-parent' | 空块的 CSS 类(可视化构建器) |
containerProps | Record<string, any> | {} | 绑定到容器的其他属性 |
showEmptyState | boolean | true | 当没有块时显示空状态 |
emptyStateClass | string | 'contentstack-empty-state' | 空状态的 CSS 类 |
emptyStateMessage | string | '没有内容块可用' | 空状态中显示的消息 |
高级属性
| 属性 | 类型 | 默认 | 描述 |
|---|---|---|---|
keyField | string | '_metadata.uid' | 块的自定义键字段 |
autoExtractBlockName | boolean | true | 从对象键中自动提取块名称 |
blockNamePrefix | string | '' | 要从块名称中删除的前缀 |
SEO 元数据支持(新功能)
该组件在使用自动获取模式时可以自动设置 SEO 元数据,使用 Nuxt 的原生 useSeoMeta 类型和功能。
简单用法
<template>
<!-- Pass SEO metadata directly using useSeoMeta format -->
<ContentstackModularBlocks
content-type-uid="page"
:url="$route.path"
:seo-meta="{
title: 'My Page Title',
description: 'My page description',
ogTitle: 'My Social Title',
ogDescription: 'My social description',
ogImage: 'https://example.com/image.jpg',
twitterCard: 'summary_large_image',
}"
:component-map="componentMapping"
/>
</template>
来自条目数据的动态 SEO
<script setup>
// Get entry data first, then use it for SEO
const { data: page } = await useGetEntryByUrl({
contentTypeUid: "page",
url: useRoute().path,
});
// Create SEO object from entry data
const seoMeta = computed(() => ({
title: page.value?.seo_title || page.value?.title,
description: page.value?.seo_description || page.value?.description,
ogTitle: page.value?.social_title || page.value?.title,
ogDescription: page.value?.social_description || page.value?.description,
ogImage: page.value?.featured_image?.url,
canonical: `https://example.com${page.value?.url}`,
robots: page.value?.no_index ? "noindex,nofollow" : "index,follow",
}));
</script>
<template>
<!-- Use computed SEO metadata -->
<ContentstackModularBlocks
:blocks="page?.components"
:seo-meta="seoMeta"
:component-map="componentMapping"
/>
</template>
从条目数据自动生成 SEO
当使用自动获取模式时,您可以从获取的条目数据中自动生成 SEO 元数据
<template>
<!-- Auto-generate SEO using default field mapping -->
<ContentstackModularBlocks
content-type-uid="page"
:url="$route.path"
:auto-seo-meta="true"
:component-map="componentMapping"
/>
</template>
自定义字段映射
<template>
<!-- Custom field mapping for SEO generation -->
<ContentstackModularBlocks
content-type-uid="page"
:url="$route.path"
:auto-seo-meta="{
title: 'page_title|title',
description: 'meta_description|description',
ogTitle: 'social_title|page_title|title',
ogImage: 'featured_image.url',
canonical: 'canonical_url',
twitterCard: 'summary_large_image',
}"
:component-map="componentMapping"
/>
</template>
默认自动 SEO 字段映射
当 autoSeoMeta: true 时,这些字段会自动映射
| SEO 元标签 | 条目字段(回退顺序) |
|---|---|
title | seo_title → title → name |
描述 | seo_description → description → summary |
ogTitle | seo_title → title → name |
ogDescription | seo_description → description → summary |
ogImage | featured_image.url → og_image.url → image.url |
结合手动和自动 SEO
<template>
<!-- Auto-generate base SEO, override specific fields -->
<ContentstackModularBlocks
content-type-uid="page"
:url="$route.path"
:auto-seo-meta="true"
:seo-meta="{
canonical: 'https://example.com' + $route.path,
robots: 'index,follow',
}"
:component-map="componentMapping"
/>
</template>
支持所有 useSeoMeta 选项
由于我们将 seoMeta prop 直接传递给 Nuxt 的 useSeoMeta,因此您可以使用 100 多个受支持的元标签中的任何一个
优点
- ✅ XSS 安全:使用 Nuxt 内置的
useSeoMeta进行安全的元标签处理 - ✅ TypeScript 支持:具有 100 多种元标签类型的完整类型安全
- ✅ 自动生成:自动从获取的条目数据中提取 SEO
- ✅ 灵活映射:支持回退的自定义字段映射
- ✅ 优先级系统:手动
seoMeta覆盖自动生成的值 - ✅ SSR 优先:SEO 元数据在服务器端渲染期间设置,以实现最佳 SEO
- ✅ 搜索引擎就绪:爬虫访问您的页面时可用的元标签
- ✅ 灵活:可选择自动检测、自定义映射或基于函数逻辑
数据结构支持
该组件支持两种常见的 Contentstack 模块块结构
自动提取(默认)
{
"components": [
{
"hero": {
"title": "Welcome",
"subtitle": "To our site"
},
"_metadata": { "uid": "hero_123" }
},
{
"grid": {
"columns": 3,
"items": [...]
},
"_metadata": { "uid": "grid_456" }
}
]
}
基于内容类型
{
"modular_blocks": [
{
"_content_type_uid": "hero_block",
"title": "Welcome",
"subtitle": "To our site",
"_metadata": { "uid": "hero_123" }
},
{
"_content_type_uid": "grid_block",
"columns": 3,
"items": [...],
"_metadata": { "uid": "grid_456" }
}
]
}
组件属性
每个渲染的组件都会接收
// Original block props
{
title: "Welcome",
subtitle: "To our site",
// ... other block fields
// Additional meta props
blockType: "hero",
blockMetadata: { uid: "hero_123", ... }
}
实时预览集成
组件自动添加实时预览属性
<section class="contentstack-modular-blocks">
<component
:is="Hero"
:title="Welcome"
data-block-type="hero"
data-block-index="0"
data-cslp="hero.title"
/>
</section>
插槽
该组件提供了几个插槽,用于自定义不同状态
| 插槽 | 描述 | 可用时机 |
|---|---|---|
loading | 自定义加载状态内容 | 自动获取已启用且数据正在加载 |
error | 自定义错误状态内容 | 自动获取失败或遇到错误 |
empty | 自定义空状态内容 | 没有可用块可渲染 |
<template>
<ContentstackModularBlocks
content-type-uid="page"
:url="$route.path"
:component-map="componentMapping"
>
<!-- Custom loading spinner -->
<template #loading>
<div class="flex items-center justify-center py-12">
<div
class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"
></div>
<span class="ml-3">Loading page content...</span>
</div>
</template>
<!-- Custom error message -->
<template #error>
<div class="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
<h3 class="text-red-800 font-semibold">Content Unavailable</h3>
<p class="text-red-600 mt-2">
Unable to load page content. Please try again later.
</p>
</div>
</template>
<!-- Custom empty state -->
<template #empty>
<div class="text-center py-12 text-gray-500">
<h3 class="text-lg font-medium">No Content Available</h3>
<p class="mt-2">This page doesn't have any content blocks yet.</p>
</div>
</template>
</ContentstackModularBlocks>
</template>
错误处理
- 缺失组件:回退到
fallbackComponent - 空块:显示可配置的空状态(可通过
#empty插槽自定义) - 无效数据:优雅地处理格式错误的块数据
- 缺失键:使用基于索引的键作为回退
- 自动获取错误:显示错误状态(可通过
#error插槽自定义) - 加载状态:在自动获取期间显示加载状态(可通过
#loading插槽自定义)
ContentstackFallbackBlock
该模块包含一个内置的回退组件,为未映射的块类型提供了开发者友好的显示。该组件自动:
- 显示块标题(来自
title、name、heading或blockType字段) - 在样式徽章中显示块类型
- 在可展开的详细信息部分将所有属性渲染为格式化的 JSON
- 提供有关如何正确映射组件的有用指导
- 支持暗模式以提供更好的开发者体验
功能
- 🎨 风格化界面,具有清晰的视觉层次结构
- 📱 响应式设计,适用于所有屏幕尺寸
- 🌙 暗模式支持,通过
prefers-color-scheme - 🔍 可折叠 JSON,避免 UI 混乱
- 🛠️ 开发者提示,显示如何修复未映射的组件
示例输出
┌─────────────────────────────────────┐
│ Welcome Hero Type: hero │
├─────────────────────────────────────┤
│ ▶ View Props │
│ { │
│ "title": "Welcome Hero", │
│ "subtitle": "Get started now", │
│ "cta_text": "Learn More" │
│ } │
├─────────────────────────────────────┤
│ This is a fallback component. │
│ Map "hero" to a proper Vue component│
└─────────────────────────────────────┘
您可以通过提供自己的 fallbackComponent 来覆盖此设置
<ContentstackModularBlocks
:blocks="page.components"
:component-map="componentMapping"
:fallback-component="MyCustomFallback"
/>
可组合项
此模块提供了几个可组合项,用于处理 Contentstack 内容。所有可组合项都支持实时预览、个性化并使用 Nuxt 的缓存系统。
useGetEntryByUrl
使用 URL 字段查询任何条目。监听实时编辑更改并支持个性化。
const { data: page } = await useGetEntryByUrl<Page>({
contentTypeUid: "page",
url: "/about",
referenceFieldPath: ["reference.fields"],
jsonRtePath: ["rich_text_field"],
locale: "en-us",
replaceHtmlCslp: true,
});
useGetEntry
通过其 UID 获取单个条目。
const { data: article } = await useGetEntry<Article>({
contentTypeUid: "article",
entryUid: "your_entry_uid",
referenceFieldPath: ["author", "category"],
jsonRtePath: ["content"],
locale: "en-us",
});
useGetEntries
通过筛选、分页和排序获取多个条目。
const { data: articles } = await useGetEntries<Article>({
contentTypeUid: "article",
referenceFieldPath: ["author"],
locale: "en-us",
limit: 10,
skip: 0,
orderBy: "created_at",
includeCount: true,
where: {
status: "published",
published_at: { $gte: "2024-01-01" },
tags: { $exists: true },
},
});
// Access results
console.log(articles.value?.entries); // Article[]
console.log(articles.value?.count); // Total count if includeCount: true
useGetAsset
通过其 UID 获取单个资产。
const { data: image } = await useGetAsset<Asset>({
assetUid: "your_asset_uid",
locale: "en-us",
});
useGetAssets
通过筛选和分页获取多个资产。
const { data: images } = await useGetAssets<Asset>({
locale: "en-us",
limit: 20,
orderBy: "created_at",
where: {
content_type: "image/jpeg",
// Note: Asset filtering is limited; most filters are applied client-side
},
});
查询运算符
条目可组合项(useGetEntries、useGetEntry、useGetEntryByUrl)在 where 参数中支持高级查询运算符
注意:资产筛选的服务器端支持有限。useGetAssets 可组合项在获取后将大多数筛选器应用于客户端。
where: {
// Exact match
status: "published",
// Array contains
tags: ["tech", "news"],
// Comparison operators
view_count: { $gt: 1000 },
created_at: { $gte: "2024-01-01", $lt: "2024-12-31" },
// Existence checks
featured_image: { $exists: true },
// Pattern matching
title: { $regex: "nuxt.*contentstack" },
// Not equal
author: { $ne: "guest" }
}
@nuxt/image 集成
此模块包含一个自定义的 @nuxt/image 提供程序,用于与 Contentstack 的图像交付 API 无缝集成。
设置
- 安装 @nuxt/image
npm install @nuxt/image
- 将这两个模块添加到您的
nuxt.config.ts
export default defineNuxtConfig({
modules: ["nuxt-contentstack", "@nuxt/image"],
// Configure @nuxt/image to use Contentstack provider
image: {
providers: {
contentstack: {
name: "contentstack",
provider:
"node_modules/nuxt-contentstack/dist/runtime/providers/contentstack",
options: {},
},
},
// Optional: Set Contentstack as default provider
provider: "contentstack",
},
});
使用
配置完成后,您可以直接使用 Contentstack 资产 URL 的 <NuxtImg> 和 <NuxtPicture> 组件 - 无需将资产 UID 或版本 UID 作为修饰符传递
<template>
<!-- Basic usage with Contentstack assets -->
<NuxtImg
:src="page.image.url"
:alt="page.image.title"
width="800"
height="400"
:modifiers="{
auto: 'webp,compress',
quality: 90,
}"
provider="contentstack"
/>
<!-- Responsive image with automatic optimization -->
<NuxtImg
:src="hero.image.url"
:alt="hero.image.title"
sizes="100vw sm:50vw lg:33vw"
densities="1x 2x"
:modifiers="{
auto: 'webp,compress',
quality: 90,
}"
provider="contentstack"
/>
<!-- Advanced transformations -->
<NuxtImg
:src="gallery.image.url"
width="600"
height="400"
fit="cover"
:modifiers="{
blur: 5,
brightness: 110,
contrast: 120,
saturation: 130,
}"
provider="contentstack"
/>
<!-- Art direction for different devices -->
<NuxtPicture
:src="article.featured_image.url"
:imgAttrs="{ alt: article.title }"
sizes="100vw md:50vw"
:modifiers="{
auto: 'webp,compress',
quality: 85,
}"
provider="contentstack"
/>
</template>
优点
- ✅ 自动图像优化,支持 WebP 和压缩
- ✅ 响应式图像,支持尺寸和密度
- ✅ Contentstack 转换,通过图像交付 API
- ✅ 懒加载和性能优化
- ✅ 艺术方向支持,通过 NuxtPicture
- ✅ 开发者体验 - 熟悉的 @nuxt/image API
可用修饰符
Contentstack 提供程序支持所有标准 @nuxt/image 修饰符以及 Contentstack 特定的转换
// Common modifiers
:modifiers="{
// Image optimization
auto: 'webp,compress',
quality: 90,
// Dimensions and cropping
width: 800,
height: 400,
fit: 'cover', // crop, bounds, fill, scale
// Effects
blur: 5,
brightness: 110,
contrast: 120,
saturation: 130,
// Format
format: 'webp',
}"
useImageTransform 可组合项
对于需要对图像转换进行编程控制的高级用例,您可以使用 useImageTransform 可组合项。当您需要动态应用转换或使用常规 <img> 标签而不是 <NuxtImg> 时,这尤其有用。
const { transformedUrl, updateTransform, resetTransform } = useImageTransform(
originalImageUrl,
{
width: 800,
height: 600,
quality: 80,
format: "webp",
fit: "crop"
}
);
// Use in template
<img :src="transformedUrl" alt="Transformed image" />
// Update transforms reactively
updateTransform({ width: 1200, quality: 90 });
// Advanced transforms
const { transformedUrl: advancedUrl } = useImageTransform(imageUrl, {
width: 800,
height: 600,
quality: 85,
format: "webp",
overlay: {
relativeURL: "/watermark.png",
align: "bottom-right",
width: "20p" // 20% of base image
},
sharpen: {
amount: 5,
radius: 2,
threshold: 0
},
saturation: 10,
brightness: 5
});
何时使用 useImageTransform 与 <NuxtImg>
- 在大多数情况下使用
<NuxtImg>- 它经过优化,支持响应式图像,并处理懒加载 - 当您需要动态转换、使用常规
<img>标签或构建自定义图像组件时,使用useImageTransform
支持的图像转换
- 尺寸:
width、height、dpr(设备像素比) - 质量和格式:
quality、format、auto优化 - 裁剪:
fit、crop、trim - 效果:
blur、saturation、brightness、contrast、sharpen - 叠加:添加水印或其他图像
- 高级:
orient、pad、bg-color、frame、resizeFilter
贡献
本地开发
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# Develop with the playground
npm run dev
# Build the playground
npm run dev:build
# Run ESLint
npm run lint
# Run Vitest
npm run test
npm run test:watch
# Release new version
npm run release