Appearance
CMS Framework Adapters
Beyond the Vue-based @duffcloudservices/cms, Duff Cloud Services ships CMS adapters for other frontend frameworks so managed content and SEO work the same way on React, Angular, and Astro sites. All of them build on one shared core package and one shared configuration contract, which is described once below and then applied per framework.
The shared contract
Every adapter reads the same two files from your repository's .dcs/ directory:
content.yaml— versioned text content, with aglobalblock and per-pageoverrides keyed by text keys such ashero.title.seo.yaml— versioned SEO configuration: aglobalblock (site name, default title, title template, default images) and per-pagetitle, description, and Open Graph settings.
yaml
# .dcs/content.yaml
version: 1
global:
nav.home: Home
pages:
home:
hero.title: Welcome to Our SiteThe pattern is identical everywhere: text is resolved by page and key (page-specific values override globals), and SEO is resolved per page and merged with your global defaults. Only the framework-specific glue changes.
Under the hood every adapter depends on @duffcloudservices/cms-core (current version 0.6.2), which provides the shared TypeScript types, the content and SEO resolution utilities, the YAML loaders, and the runtime-fetch helper. The three framework adapters are each at version 0.3.2.
React — @duffcloudservices/cms-react
React hooks plus Vite plugins. Add the content and SEO plugins to your Vite config, then use the hooks in components:
typescript
// vite.config.ts
import { dcsContentPlugin, dcsSeoPlugin } from '@duffcloudservices/cms-react/plugins'
export default defineConfig({
plugins: [react(), dcsContentPlugin(), dcsSeoPlugin()],
})tsx
import { useTextContent, useSEO } from '@duffcloudservices/cms-react'
export function Home() {
const { t } = useTextContent({ page: 'home', defaults: { 'hero.title': 'Welcome' } })
useSEO({ page: 'home' })
return <h1>{t('hero.title')}</h1>
}useTextContent({ page, defaults })returnst(key, fallback?)plus the resolved content, loading and error state, and arefresh().useSEO({ page, overrides? })resolves the page's meta tags.
Peer requirement: React ^18 or ^19.
Angular — @duffcloudservices/cms-angular
Angular services plus a post-build injection step. Register the provider, inject the services, and run the injector after ng build:
typescript
// app.config.ts
import { provideDcs } from '@duffcloudservices/cms-angular'
export const appConfig = { providers: [provideDcs()] }typescript
import { DcsContentService, DcsSeoService } from '@duffcloudservices/cms-angular'
// content.t('home', 'hero.title', 'Welcome')
// seo.setPageSeo('home')Because Angular renders differently from a Vite pipeline, content is injected into the built output with the bundled dcs-inject CLI:
bash
ng build && npx dcs-inject dist/my-appDcsContentServiceexposest(page, key, fallback?),getPageContent(page), and content signals.DcsSeoServiceexposessetPageSeo(page, overrides?)andgetSeoConfig().
Peer requirement: Angular ^17, ^18, or ^19 (@angular/core, @angular/common, @angular/platform-browser).
Astro — @duffcloudservices/cms-astro
An Astro integration plus runtime helpers. Add the integration, then call the helpers from your frontmatter:
typescript
// astro.config.mjs
import dcs from '@duffcloudservices/cms-astro'
export default defineConfig({ integrations: [dcs()] })astro
---
import { t, getPageSeo, getPageContent } from '@duffcloudservices/cms-astro'
const seo = getPageSeo('home')
---
<title>{seo?.title}</title>
<h1>{t('home', 'hero.title', 'Welcome')}</h1>t(page, key, fallback?)resolves a single text key.getPageContent(page)returns all content for a page (globals merged with page-specific).getPageSeo(page, overrides?)returns resolved SEO with optional overrides.
Peer requirement: Astro ^4 or ^5.
Core — @duffcloudservices/cms-core
The framework packages all sit on @duffcloudservices/cms-core, which you rarely import directly but which does the real work: content resolution (resolveTextKey, getPageContent), SEO resolution (resolveSeoForPage, buildMetaTags), YAML loading (loadContentYaml, loadSeoYaml), and runtime fetching (fetchRuntimeContent).
Runtime content is a premium feature
By default, content is resolved at build time from your .dcs files. Runtime fetching — pulling the latest content live from the DCS API so portal edits appear without rebuilding — is a premium-tier capability. On non-premium sites the runtime helpers simply return nothing, and your site keeps serving its build-time content.
Publishing and provenance
These packages are published to the public npm registry through GitHub Actions OIDC trusted publishing — there is no long-lived npm token to leak, and each release is tied to the workflow that produced it.
Related
- NPM Packages overview — all published DCS packages.
- @duffcloudservices/cms — the Vue composables and Vite plugins.
- @duffcloudservices/cli — site onboarding and configuration.
