@duffcloudservices/cms
Vue 3 composables and Vite plugins for DCS (Duff Cloud Services) CMS integration.
📦 Package Info
Installation
# Using pnpm (recommended)
pnpm add @duffcloudservices/cms
# Using npm
npm install @duffcloudservices/cms
# Using yarn
yarn add @duffcloudservices/cmsPeer Dependencies
This package requires:
vue^3.4.0@unhead/vue^1.9.0
Quick Start
1. Configure Vite Plugins
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { dcsContentPlugin, dcsSeoPlugin } from '@duffcloudservices/cms/plugins'
export default defineConfig({
plugins: [
vue(),
dcsContentPlugin(),
dcsSeoPlugin()
]
})For VitePress sites:
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { dcsContentPlugin, dcsSeoPlugin } from '@duffcloudservices/cms/plugins'
export default defineConfig({
vite: {
plugins: [
dcsContentPlugin(),
dcsSeoPlugin()
]
}
})2. Set Environment Variables
# .env
VITE_SITE_SLUG=your-site-slug
# Optional: for runtime overrides (premium tier)
VITE_API_BASE_URL=https://portal.duffcloudservices.com
VITE_TEXT_OVERRIDE_MODE=commit # 'commit' (default) or 'runtime'3. Use Composables
<script setup lang="ts">
import { useTextContent, useSEO } from '@duffcloudservices/cms'
// Text content with defaults
const { t } = useTextContent({
pageSlug: 'home',
defaults: {
'hero.title': 'Welcome to Our Site',
'hero.subtitle': 'Build amazing things with us',
'cta.primary': 'Get Started'
}
})
// SEO configuration
const { applyHead } = useSEO('home')
applyHead()
</script>
<template>
<section class="hero">
<h1>{{ t('hero.title') }}</h1>
<p>{{ t('hero.subtitle') }}</p>
<button>{{ t('cta.primary') }}</button>
</section>
</template>Composables
useTextContent
Provides text content management with build-time injection and optional runtime overrides.
import { useTextContent } from '@duffcloudservices/cms'
const {
t, // (key: string, fallback?: string) => string
texts, // ComputedRef<Record<string, string>>
overrides, // Ref<Record<string, string>>
isLoading, // Ref<boolean>
error, // Ref<string | null>
refresh, // () => Promise<void>
hasOverride, // (key: string) => boolean
hasBuildTimeContent, // boolean
mode // 'commit' | 'runtime'
} = useTextContent({
pageSlug: 'home',
defaults: {
'hero.title': 'Default Title'
},
fetchOnMount: true, // default: true (only matters in runtime mode)
cacheTtl: 60000 // default: 60000ms
})Content Resolution Order:
- Runtime API overrides (premium tier only)
- Build-time content from
.dcs/content.yaml - Hardcoded defaults passed to the composable
Example: Using in a Component
<script setup lang="ts">
import { useTextContent } from '@duffcloudservices/cms'
const { t, isLoading } = useTextContent({
pageSlug: 'about',
defaults: {
'hero.title': 'About Us',
'hero.description': 'Learn more about our company'
}
})
</script>
<template>
<div v-if="isLoading">Loading...</div>
<div v-else>
<h1>{{ t('hero.title') }}</h1>
<p>{{ t('hero.description') }}</p>
</div>
</template>useSEO
Provides SEO configuration with meta tags, Open Graph, Twitter Cards, and JSON-LD schemas.
import { useSEO } from '@duffcloudservices/cms'
const {
config, // ComputedRef<ResolvedPageSeo>
applyHead, // (overrides?: HeadOverrides) => void
getSchema, // () => object[]
getCanonical, // () => string
hasBuildTimeSeo // boolean
} = useSEO('home', '/') // pageSlug, optional pagePath
// Apply all meta tags
applyHead()
// Or with overrides
applyHead({
title: 'Custom Title',
description: 'Custom description',
schemas: [...getSchema(), customSchema]
})SEO Features
- Meta Tags — Title, description, keywords
- Open Graph — Facebook/LinkedIn sharing cards
- Twitter Cards — Twitter-specific meta tags
- JSON-LD Schemas — Structured data for search engines
- Canonical URLs — Prevent duplicate content issues
useReleaseNotes
Fetches release notes from the DCS Portal API.
import { useReleaseNotes } from '@duffcloudservices/cms'
const {
releaseNote, // Ref<ReleaseNote | null>
isLoading, // Ref<boolean>
error, // Ref<string | null>
refresh // () => Promise<void>
} = useReleaseNotes('1.2.0') // or 'latest'Example: Release Notes Page
<script setup lang="ts">
import { useReleaseNotes } from '@duffcloudservices/cms'
import { useRoute } from 'vue-router'
const route = useRoute()
const version = route.params.version as string || 'latest'
const { releaseNote, isLoading, error } = useReleaseNotes(version)
</script>
<template>
<div v-if="isLoading">Loading release notes...</div>
<div v-else-if="error">{{ error }}</div>
<article v-else-if="releaseNote">
<h1>Version {{ releaseNote.version }}</h1>
<time>{{ releaseNote.publishedAt }}</time>
<div v-html="releaseNote.content"></div>
</article>
</template>useSiteVersion
Gets the current site version for footer badges and version displays.
import { useSiteVersion } from '@duffcloudservices/cms'
const {
version, // Ref<string | null>
isLoading, // Ref<boolean>
releaseNotesUrl // ComputedRef<string>
} = useSiteVersion()Example: Footer Version Badge
<script setup lang="ts">
import { useSiteVersion } from '@duffcloudservices/cms'
const { version, releaseNotesUrl } = useSiteVersion()
</script>
<template>
<footer>
<a v-if="version" :href="releaseNotesUrl" class="version-badge">
v{{ version }}
</a>
</footer>
</template>Vite Plugins
dcsContentPlugin
Injects .dcs/content.yaml at build time for zero-runtime-cost text content.
import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'
// In vite.config.ts or .vitepress/config.ts
dcsContentPlugin({
contentPath: '.dcs/content.yaml', // default
debug: false // default
})This plugin reads your content.yaml file during build and makes it available to useTextContent without requiring API calls.
dcsSeoPlugin
Injects .dcs/seo.yaml at build time for SEO configuration.
import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'
dcsSeoPlugin({
seoPath: '.dcs/seo.yaml', // default
debug: false // default
})Configuration Files
.dcs/content.yaml
Text content managed by the DCS Portal:
version: 1
lastUpdated: "2025-01-01T00:00:00Z"
updatedBy: "portal"
global:
nav.home: Home
nav.about: About
footer.copyright: © 2025 My Company
pages:
home:
hero.title: Welcome to Our Site
hero.subtitle: Build amazing things
cta.primary: Get Started
about:
hero.title: About Us
hero.subtitle: Learn more about our mission.dcs/seo.yaml
SEO configuration managed by the DCS Portal:
version: 1
lastUpdated: "2025-01-01T00:00:00Z"
global:
siteName: My Site
siteUrl: https://example.com
locale: en_US
defaultTitle: My Site
defaultDescription: Build amazing things with us
titleTemplate: "%s | My Site"
social:
twitter: mycompany
linkedin: my-company
images:
logo: https://example.com/logo.png
ogDefault: https://example.com/og-image.jpg
pages:
home:
title: Welcome
description: Build amazing things with our platform
noTitleTemplate: true
openGraph:
type: website
twitter:
card: summary_large_image
about:
title: About Us
description: Learn more about our company and missionTypeScript Support
All composables and plugins are fully typed. Import types as needed:
import type {
TextContentConfig,
TextContentReturn,
SeoConfiguration,
PageSeoConfig,
ReleaseNote
} from '@duffcloudservices/cms'Migration from Manual Setup
If you're migrating from manually copied composables:
// Before (manual setup):
import { useTextContent } from '@/lib/use-text-content'
// After (npm package):
import { useTextContent } from '@duffcloudservices/cms'The API is backward compatible — no other code changes are needed.
Text Override Modes
The package supports two modes for text content:
| Mode | Description | Use Case |
|---|---|---|
commit | Content from .dcs/content.yaml at build time | Standard tier, best performance |
runtime | Live API overrides with instant updates | Premium tier, real-time editing |
Set the mode via environment variable:
VITE_TEXT_OVERRIDE_MODE=commit # Default - build-time content
VITE_TEXT_OVERRIDE_MODE=runtime # Premium - live API overridesTroubleshooting
Content not updating after build
Ensure the Vite plugins are configured and rebuild the site:
pnpm buildMissing peer dependencies
Install required peer dependencies:
pnpm add vue @unhead/vueTypeScript errors
Ensure your tsconfig.json includes the package types:
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}