Skip to content

@duffcloudservices/cms

Vue 3 composables and Vite plugins for DCS (Duff Cloud Services) CMS integration.

📦 Package Info

npm: @duffcloudservices/cms

Installation

bash
# Using pnpm (recommended)
pnpm add @duffcloudservices/cms

# Using npm
npm install @duffcloudservices/cms

# Using yarn
yarn add @duffcloudservices/cms

Peer Dependencies

This package requires:

  • vue ^3.4.0
  • @unhead/vue ^1.9.0

Quick Start

1. Configure Vite Plugins

typescript
// 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:

typescript
// .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

bash
# .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

vue
<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.

typescript
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:

  1. Runtime API overrides (premium tier only)
  2. Build-time content from .dcs/content.yaml
  3. Hardcoded defaults passed to the composable

Example: Using in a Component

vue
<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.

typescript
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.

typescript
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

vue
<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.

typescript
import { useSiteVersion } from '@duffcloudservices/cms'

const {
  version,          // Ref<string | null>
  isLoading,        // Ref<boolean>
  releaseNotesUrl   // ComputedRef<string>
} = useSiteVersion()
vue
<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.

typescript
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.

typescript
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:

yaml
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:

yaml
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 mission

TypeScript Support

All composables and plugins are fully typed. Import types as needed:

typescript
import type {
  TextContentConfig,
  TextContentReturn,
  SeoConfiguration,
  PageSeoConfig,
  ReleaseNote
} from '@duffcloudservices/cms'

Migration from Manual Setup

If you're migrating from manually copied composables:

typescript
// 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:

ModeDescriptionUse Case
commitContent from .dcs/content.yaml at build timeStandard tier, best performance
runtimeLive API overrides with instant updatesPremium tier, real-time editing

Set the mode via environment variable:

bash
VITE_TEXT_OVERRIDE_MODE=commit   # Default - build-time content
VITE_TEXT_OVERRIDE_MODE=runtime  # Premium - live API overrides

Troubleshooting

Content not updating after build

Ensure the Vite plugins are configured and rebuild the site:

bash
pnpm build

Missing peer dependencies

Install required peer dependencies:

bash
pnpm add vue @unhead/vue

TypeScript errors

Ensure your tsconfig.json includes the package types:

json
{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}

Duff Cloud Services Documentation