Skip to content

VitePress Sites

Partner Documentation

VitePress is the recommended framework for DCS sites. This guide covers VitePress-specific integration.

Why VitePress?

VitePress offers:

  • Fast — Built on Vite, near-instant hot reload
  • Simple — Markdown-based content
  • Flexible — Vue components for custom features
  • SEO-friendly — Static generation with good defaults

Project Structure

A typical DCS VitePress site:

my-site/
├── .dcs/
│   ├── config.yaml
│   └── pages.yaml
├── .github/
│   ├── copilot-instructions.md
│   └── workflows/
│       └── deploy.yml
├── .vitepress/
│   ├── config.ts
│   └── theme/
│       └── index.ts
├── src/
│   ├── lib/
│   │   └── use-text-content.ts
│   └── components/
├── docs/
│   ├── index.md
│   ├── about.md
│   └── blog/
├── public/
│   └── images/
├── .env
└── package.json

Installation

1. Create VitePress Project

bash
pnpm create vitepress

2. Add DCS Integration

bash
# Install DCS client (when available)
pnpm add @dcs/client

# Or create manual integration files

3. Configure VitePress

Update .vitepress/config.ts:

typescript
import { defineConfig } from 'vitepress'

export default defineConfig({
  title: 'My Site',
  description: 'A DCS-powered site',
  
  themeConfig: {
    // Your theme configuration
  },
  
  vite: {
    define: {
      // Make env vars available
      'import.meta.env.VITE_DCS_API': JSON.stringify(process.env.VITE_DCS_API),
      'import.meta.env.VITE_SITE_ID': JSON.stringify(process.env.VITE_SITE_ID),
    }
  }
})

Text Content Integration

Create the Composable

Create src/lib/use-text-content.ts:

typescript
import { ref, onMounted, readonly } from 'vue'

interface TextOverrides {
  [key: string]: string
}

const textOverrides = ref<TextOverrides>({})
const isLoading = ref(true)
const error = ref<Error | null>(null)

let fetchPromise: Promise<void> | null = null

async function fetchOverrides(): Promise<void> {
  // Only fetch once
  if (fetchPromise) return fetchPromise
  
  fetchPromise = (async () => {
    try {
      const apiUrl = import.meta.env.VITE_DCS_API
      const siteId = import.meta.env.VITE_SITE_ID
      
      if (!apiUrl || !siteId) {
        console.warn('DCS API not configured')
        return
      }
      
      const response = await fetch(`${apiUrl}/sites/${siteId}/text-content`)
      
      if (response.ok) {
        const data = await response.json()
        textOverrides.value = data.overrides || {}
      }
    } catch (e) {
      error.value = e as Error
      console.error('Failed to fetch text overrides:', e)
    } finally {
      isLoading.value = false
    }
  })()
  
  return fetchPromise
}

export function useTextContent() {
  onMounted(() => {
    fetchOverrides()
  })
  
  function t(key: string, defaultValue: string): string {
    return textOverrides.value[key] ?? defaultValue
  }
  
  return {
    t,
    isLoading: readonly(isLoading),
    error: readonly(error)
  }
}

Use in Markdown

Create a Vue component for editable sections:

vue
<!-- src/components/EditableText.vue -->
<script setup lang="ts">
import { useTextContent } from '../lib/use-text-content'

const props = defineProps<{
  textKey: string
  default: string
  tag?: string
}>()

const { t, isLoading } = useTextContent()
</script>

<template>
  <component :is="tag || 'span'" :class="{ 'opacity-50': isLoading }">
    {{ t(textKey, default) }}
  </component>
</template>

Use in markdown files:

markdown
---
title: Home
---

<script setup>
import EditableText from '../src/components/EditableText.vue'
</script>

# <EditableText text-key="home.hero.title" default="Welcome" tag="span" />

<EditableText 
  text-key="home.hero.description" 
  default="This is the default description text."
  tag="p"
/>

SEO Integration

Dynamic Frontmatter

Create a script to fetch SEO at build time:

typescript
// scripts/fetch-seo.ts
import { writeFileSync } from 'fs'

async function fetchSEO() {
  const response = await fetch(`${process.env.VITE_DCS_API}/sites/${process.env.VITE_SITE_ID}/seo`)
  const seo = await response.json()
  
  // Write to a JSON file for build-time access
  writeFileSync('.vitepress/seo-data.json', JSON.stringify(seo))
}

Head Configuration

typescript
// .vitepress/config.ts
import seoData from './seo-data.json'

export default defineConfig({
  transformPageData(pageData) {
    const pageSeo = seoData[pageData.relativePath]
    
    if (pageSeo) {
      pageData.title = pageSeo.title
      pageData.description = pageSeo.description
    }
  }
})

Blog Integration

Fetch Posts at Build Time

typescript
// scripts/fetch-blog.ts
import { writeFileSync, mkdirSync } from 'fs'

async function fetchBlogPosts() {
  const response = await fetch(`${process.env.VITE_DCS_API}/sites/${process.env.VITE_SITE_ID}/blog/posts`)
  const posts = await response.json()
  
  // Generate markdown files for each post
  mkdirSync('docs/blog', { recursive: true })
  
  for (const post of posts) {
    const markdown = `---
title: ${post.title}
date: ${post.publishedAt}
author: ${post.author}
---

${post.content}
`
    writeFileSync(`docs/blog/${post.slug}.md`, markdown)
  }
}

Blog Index Page

vue
<!-- docs/blog/index.md -->
<script setup>
import { data as posts } from './posts.data.ts'
</script>

# Blog

<div v-for="post in posts" :key="post.slug" class="post-card">
  <h2><a :href="post.url">{{ post.title }}</a></h2>
  <p>{{ post.excerpt }}</p>
  <time>{{ post.date }}</time>
</div>

Custom Theme

Extend Default Theme

typescript
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import EditableText from '../../src/components/EditableText.vue'
import './custom.css'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    // Register global components
    app.component('EditableText', EditableText)
  }
} satisfies Theme

Custom Styles

css
/* .vitepress/theme/custom.css */
:root {
  --vp-c-brand-1: #3b82f6;
  --vp-c-brand-2: #60a5fa;
  --vp-c-brand-3: #2563eb;
}

/* Your custom styles */

Build Configuration

Package Scripts

json
{
  "scripts": {
    "dev": "vitepress dev docs",
    "build": "pnpm fetch:content && vitepress build docs",
    "preview": "vitepress preview docs",
    "fetch:content": "tsx scripts/fetch-content.ts"
  }
}

Environment Variables

bash
# .env
VITE_DCS_API=https://portal.duffcloudservices.com/api
VITE_SITE_ID=your-site-id

Deployment

GitHub Actions Workflow

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          
      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 9
          
      - name: Install dependencies
        run: pnpm install
        
      - name: Fetch content
        run: pnpm fetch:content
        env:
          VITE_DCS_API: ${{ secrets.VITE_DCS_API }}
          VITE_SITE_ID: ${{ secrets.VITE_SITE_ID }}
          
      - name: Build
        run: pnpm build
        
      - name: Deploy to Azure SWA
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_SWA_TOKEN }}
          action: 'upload'
          app_location: 'docs/.vitepress/dist'

Best Practices

Performance

  • Use content-visibility: auto for long pages
  • Lazy load images below the fold
  • Minimize custom JavaScript

SEO

  • Set proper frontmatter for each page
  • Use semantic HTML
  • Include alt text for images

Accessibility

  • Use proper heading hierarchy
  • Ensure sufficient color contrast
  • Test with keyboard navigation

Troubleshooting

Text Not Updating

  1. Check environment variables
  2. Verify API endpoint is accessible
  3. Check browser console for errors

Build Failures

  1. Ensure all content scripts run before build
  2. Check for missing dependencies
  3. Verify file paths are correct

Hot Reload Issues

  1. Clear Vite cache: rm -rf node_modules/.vite
  2. Restart dev server
  3. Check for syntax errors

Next Steps

Duff Cloud Services Documentation