Skip to content

多版本文档管理

本文档介绍如何在 VitePress 中实现多版本文档管理,让用户可以在不同版本之间切换。

概述

为什么需要多版本文档?

  • 框架库文档:如 Vue 2 / Vue 3
  • API 文档:v1.x / v2.x 接口
  • 产品文档:不同产品版本的功能说明
  • 历史归档:保留旧版本文档供用户查阅

实现方式

方式适用场景复杂度
子目录方式简单多版本
多仓库方式大型项目⭐⭐⭐
部署重定向需要独立域名⭐⭐

子目录方式(推荐)

目录结构

docs/
├── index.md              # 首页(版本选择)
├── v1/                   # v1 版本
│   ├── index.md
│   ├── guide/
│   └── api/
├── v2/                   # v2 版本
│   ├── index.md
│   ├── guide/
│   └── api/
└── .vitepress/
    └── config.mts

配置文件

ts
// docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'

export default defineConfig({
  title: 'My Project',
  
  // 多语言/多版本配置
  locales: {
    '/': {
      label: 'v2.x',
      lang: 'zh-CN',
      title: 'My Project v2',
      description: '最新版本',
      themeConfig: {
        nav: [
          { text: '指南', link: '/guide/' },
          { text: 'API', link: '/api/' }
        ],
        sidebar: {
          '/guide/': [
            {
              text: '开始',
              items: [
                { text: '介绍', link: '/guide/' },
                { text: '快速开始', link: '/guide/getting-started' }
              ]
            }
          ]
        }
      }
    },
    '/v1/': {
      label: 'v1.x',
      lang: 'zh-CN',
      title: 'My Project v1',
      description: '历史版本',
      themeConfig: {
        nav: [
          { text: '指南', link: '/v1/guide/' },
          { text: 'API', link: '/v1/api/' }
        ],
        sidebar: {
          '/v1/guide/': [
            {
              text: '开始',
              items: [
                { text: '介绍', link: '/v1/guide/' },
                { text: '快速开始', link: '/v1/guide/getting-started' }
              ]
            }
          ]
        }
      }
    }
  },
  
  themeConfig: {
    // 版本切换器(使用 locales 的 label)
  }
})

版本选择首页

markdown
<!-- docs/index.md -->
---
layout: home
hero:
  name: My Project
  text: 多版本文档
  tagline: 选择您需要的版本
actions:
  - theme: brand
    text: v2.x (最新)
    link: /guide/
  - theme: alt
    text: v1.x (稳定)
    link: /v1/guide/
---

独立配置文件方式

对于差异较大的版本,可以使用独立配置:

目录结构

docs/
├── v1/
│   ├── index.md
│   ├── .vitepress/
│   │   └── config.mts    # v1 独立配置
│   └── guide/
├── v2/
│   ├── index.md
│   ├── .vitepress/
│   │   └── config.mts    # v2 独立配置
│   └── guide/
└── index.md              # 版本选择页

独立构建脚本

json
// package.json
{
  "scripts": {
    "build": "npm run build:v1 && npm run build:v2",
    "build:v1": "vitepress build docs/v1 --outDir ../dist/v1",
    "build:v2": "vitepress build docs/v2 --outDir ../dist",
    "dev:v1": "vitepress dev docs/v1",
    "dev:v2": "vitepress dev docs/v2"
  }
}

版本选择器组件

自定义版本选择器

vue
<!-- docs/.vitepress/theme/components/VersionSelector.vue -->
<script setup>
import { ref } from 'vue'
import { useData } from 'vitepress'

const { lang } = useData()

const versions = [
  { label: 'v2.x (最新)', link: '/guide/' },
  { label: 'v1.x (稳定)', link: '/v1/guide/' },
  { label: 'v0.x (归档)', link: '/v0/guide/' }
]

const isOpen = ref(false)

function toggleDropdown() {
  isOpen.value = !isOpen.value
}

function selectVersion(link) {
  isOpen.value = false
  window.location.href = link
}
</script>

<template>
  <div class="version-selector">
    <button class="selector-button" @click="toggleDropdown">
      <span>{{ lang === 'v1' ? 'v1.x' : 'v2.x' }}</span>
      <span class="arrow">▼</span>
    </button>
    <ul v-show="isOpen" class="dropdown">
      <li 
        v-for="v in versions" 
        :key="v.link"
        @click="selectVersion(v.link)"
      >
        {{ v.label }}
      </li>
    </ul>
  </div>
</template>

<style scoped>
.version-selector {
  position: relative;
  margin-right: 12px;
}

.selector-button {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px;
  border: 1px solid var(--vp-c-divider);
  border-radius: 6px;
  background: var(--vp-c-bg);
  cursor: pointer;
  font-size: 14px;
}

.arrow {
  font-size: 10px;
}

.dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 150px;
  padding: 8px 0;
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-divider);
  border-radius: 8px;
  box-shadow: var(--vp-shadow-2);
  list-style: none;
  margin: 4px 0 0;
  z-index: 100;
}

.dropdown li {
  padding: 8px 16px;
  cursor: pointer;
  transition: background 0.2s;
}

.dropdown li:hover {
  background: var(--vp-c-bg-soft);
}
</style>

集成到导航栏

ts
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import VersionSelector from './components/VersionSelector.vue'
import { h } from 'vue'

export default {
  extends: DefaultTheme,
  Layout: () => h(DefaultTheme.Layout, null, {
    'nav-bar-content-before': () => h(VersionSelector)
  })
}

版本徽章

添加版本徽章

vue
<!-- docs/.vitepress/theme/components/VersionBadge.vue -->
<script setup>
defineProps({
  version: {
    type: String,
    default: 'latest'
  }
})

const versionColors = {
  latest: 'tip',
  stable: 'info',
  deprecated: 'warning',
  archived: 'danger'
}
</script>

<template>
  <span 
    class="version-badge" 
    :class="versionColors[version]"
  >
    {{ version }}
  </span>
</template>

<style scoped>
.version-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 600;
}

.tip {
  background: var(--vp-c-brand-soft);
  color: var(--vp-c-brand-1);
}

.info {
  background: var(--vp-c-info-soft);
  color: var(--vp-c-info-1);
}

.warning {
  background: var(--vp-c-warning-soft);
  color: var(--vp-c-warning-1);
}

.danger {
  background: var(--vp-c-danger-soft);
  color: var(--vp-c-danger-1);
}
</style>

在页面中使用

markdown
# 快速开始 <VersionBadge version="latest" />

本章节介绍最新版本的使用方法。

版本间链接处理

跨版本链接警告

vue
<!-- docs/.vitepress/theme/components/VersionLink.vue -->
<script setup>
import { useData } from 'vitepress'

const props = defineProps({
  href: String,
  text: String
})

const { lang } = useData()

function handleClick(e) {
  // 检测是否跨版本
  const isCrossVersion = props.href.startsWith('/v') && 
                         !props.href.startsWith(`/${lang.value}`)
  
  if (isCrossVersion) {
    const confirmed = confirm('即将跳转到不同版本的文档,确定继续?')
    if (!confirmed) {
      e.preventDefault()
    }
  }
}
</script>

<template>
  <a :href="href" @click="handleClick">{{ text }}</a>
</template>

版本提示横幅

vue
<!-- docs/.vitepress/theme/components/VersionBanner.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { useData } from 'vitepress'

const { lang } = useData()
const showBanner = ref(false)

onMounted(() => {
  // 检测是否为旧版本
  if (lang.value === 'v1') {
    showBanner.value = true
  }
})

function dismiss() {
  showBanner.value = false
}
</script>

<template>
  <div v-if="showBanner" class="version-banner">
    <span>📚 你正在查看旧版本文档,</span>
    <a href="/guide/">查看最新版本</a>
    <button @click="dismiss">×</button>
  </div>
</template>

<style scoped>
.version-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 24px;
  background: var(--vp-c-warning-soft);
  border-bottom: 1px solid var(--vp-c-divider);
  font-size: 14px;
}

.version-banner a {
  color: var(--vp-c-brand-1);
  text-decoration: underline;
}

.version-banner button {
  margin-left: auto;
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  opacity: 0.6;
}

.version-banner button:hover {
  opacity: 1;
}
</style>

自动版本检测

检测 package.json 版本

ts
// docs/.vitepress/utils/version.ts
import { readFileSync } from 'fs'
import { resolve } from 'path'

export function getPackageVersion() {
  try {
    const pkg = JSON.parse(
      readFileSync(resolve(__dirname, '../../../package.json'), 'utf-8')
    )
    return pkg.version
  } catch {
    return 'unknown'
  }
}

在配置中使用:

ts
// docs/.vitepress/config.mts
import { getPackageVersion } from './utils/version'

export default defineConfig({
  define: {
    __VERSION__: JSON.stringify(getPackageVersion())
  }
})

在组件中使用:

vue
<script setup>
declare const __VERSION__: string
</script>

<template>
  <footer>当前版本: {{ __VERSION__ }}</footer>
</template>

构建与部署

构建脚本

bash
#!/bin/bash
# build.sh

# 清理输出目录
rm -rf dist

# 构建所有版本
echo "Building v2..."
vitepress build docs --outDir dist

echo "Building v1..."
vitepress build docs/v1 --outDir dist/v1

echo "Building v0..."
vitepress build docs/v0 --outDir dist/v0

echo "All versions built!"

GitHub Actions 配置

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

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'pnpm'
      
      - run: pnpm install
      
      - name: Build all versions
        run: |
          pnpm build
      
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist

URL 重定向

Nginx 配置

nginx
# 默认重定向到最新版本
server {
    listen 80;
    server_name docs.example.com;
    
    location = / {
        return 301 /guide/;
    }
    
    # v1 版本
    location /v1/ {
        alias /var/www/v1/;
    }
    
    # v2 版本(默认)
    location / {
        alias /var/www/v2/;
    }
}

Vercel 配置

json
// vercel.json
{
  "rewrites": [
    { "source": "/v1/:path*", "destination": "/v1/:path*" },
    { "source": "/:path*", "destination": "/:path*" }
  ],
  "redirects": [
    { "source": "/", "destination": "/guide/", "permanent": true }
  ]
}

内容同步策略

共享组件

docs/
├── shared/                # 共享内容
│   ├── components/       # 共享组件
│   └── styles/           # 共享样式
├── v1/
│   └── .vitepress/
│       └── config.mts    # 引用 shared
└── v2/
    └── .vitepress/
        └── config.mts    # 引用 shared

引用共享组件

ts
// docs/v2/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import SharedComponent from '../../shared/components/SharedComponent.vue'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    app.component('SharedComponent', SharedComponent)
  }
}

最佳实践

1. 版本命名规范

  • latest - 最新版本
  • v2.x - 主版本
  • v1.x - 旧版本
  • next - 预览版

2. 版本生命周期

状态说明徽章颜色
Latest最新稳定版绿色
Stable稳定维护版蓝色
LTS长期支持版紫色
Deprecated即将废弃橙色
Archived已归档灰色

3. 版本提示

  • 旧版本显示升级提示
  • 废弃版本显示迁移指南
  • 归档版本标记为只读

4. SEO 考虑

html
<!-- 在旧版本页面添加 canonical 链接 -->
<link rel="canonical" href="https://docs.example.com/guide/" />

<!-- 添加 alternate 链接 -->
<link rel="alternate" hreflang="x-default" href="https://docs.example.com/guide/" />
<link rel="alternate" hreflang="en" href="https://docs.example.com/v1/guide/" />

常见问题

Q: 如何自动同步版本间的内容?

A: 可以编写脚本从主版本复制到其他版本:

bash
#!/bin/bash
# sync-content.sh

# 从 v2 复制共享内容到 v1
cp -r docs/shared/* docs/v1/
cp -r docs/shared/* docs/v2/

Q: 版本过多导致构建时间长怎么办?

A: 可以并行构建:

json
{
  "scripts": {
    "build": "npm-run-all --parallel build:*",
    "build:v1": "vitepress build docs/v1 --outDir ../dist/v1",
    "build:v2": "vitepress build docs/v2 --outDir ../dist"
  }
}

下一步

学习 运行时 API 了解更多高级功能。

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献