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.jsonInstallation
1. Create VitePress Project
bash
pnpm create vitepress2. Add DCS Integration
bash
# Install DCS client (when available)
pnpm add @dcs/client
# Or create manual integration files3. 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 ThemeCustom 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-idDeployment
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: autofor 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
- Check environment variables
- Verify API endpoint is accessible
- Check browser console for errors
Build Failures
- Ensure all content scripts run before build
- Check for missing dependencies
- Verify file paths are correct
Hot Reload Issues
- Clear Vite cache:
rm -rf node_modules/.vite - Restart dev server
- Check for syntax errors
Next Steps
- Configuration Files — Detailed config reference
- GitHub Setup — Repository configuration
- Text Content API — API documentation
