Skip to content

RSS 订阅源

为你的站点生成 RSS 订阅源,让用户可以订阅更新。

使用 vitepress-plugin-rss

安装

bash
npm install vitepress-plugin-rss -D

配置

ts
// .vitepress/config.mts
import { rssPlugin } from 'vitepress-plugin-rss'

export default defineConfig({
  vite: {
    plugins: [
      rssPlugin({
        title: 'VitePress 学习指南',
        description: 'VitePress 官方中文教程',
        baseUrl: 'https://your-domain.com',
        copyright: 'Copyright 2026',
        output: {
          rss: true,      // 输出 rss.xml
          atom: true,     // 输出 atom.xml
          json: true      // 输出 feed.json
        }
      })
    ]
  }
})

手动生成 RSS

安装依赖

bash
npm install feed gray-matter -D

创建生成脚本

ts
// scripts/generate-rss.ts
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { Feed } from 'feed'

const SITE_URL = 'https://your-domain.com'
const SITE_TITLE = 'VitePress 学习指南'
const SITE_DESC = '从零开始学习 VitePress'

const feed = new Feed({
  title: SITE_TITLE,
  description: SITE_DESC,
  id: SITE_URL,
  link: SITE_URL,
  language: 'zh-CN',
  image: `${SITE_URL}/logo.png`,
  favicon: `${SITE_URL}/favicon.ico`,
  copyright: `Copyright © ${new Date().getFullYear()}`,
  updated: new Date(),
  feedLinks: {
    rss: `${SITE_URL}/rss.xml`,
    atom: `${SITE_URL}/atom.xml`,
    json: `${SITE_URL}/feed.json`
  },
  author: {
    name: 'VitePress 学习指南',
    link: SITE_URL
  }
})

interface Article {
  title: string
  path: string
  date: Date
  excerpt: string
  content: string
  tags: string[]
}

const articles: Article[] = []

function walk(dir: string) {
  const entries = fs.readdirSync(dir, { withFileTypes: true })
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)
    
    if (entry.isDirectory() && !entry.name.startsWith('.')) {
      walk(fullPath)
    } else if (entry.name.endsWith('.md')) {
      const content = fs.readFileSync(fullPath, 'utf-8')
      const { data, content: body } = matter(content)
      
      if (!data.date) continue
      
      articles.push({
        title: data.title || entry.name,
        path: fullPath.replace('docs/', '').replace('.md', ''),
        date: new Date(data.date),
        excerpt: body.slice(0, 200).replace(/[#*`\[\]]/g, ''),
        content: body,
        tags: data.tags || []
      })
    }
  }
}

walk('docs')

// 按日期排序,取最近 20 篇
articles
  .sort((a, b) => b.date.getTime() - a.date.getTime())
  .slice(0, 20)
  .forEach(article => {
    const url = `${SITE_URL}/${article.path}`
    
    feed.addItem({
      title: article.title,
      id: url,
      link: url,
      description: article.excerpt,
      content: article.content.slice(0, 1000),
      author: [{ name: 'VitePress 学习指南' }],
      date: article.date,
      category: article.tags.map(tag => ({ name: tag }))
    })
  })

// 输出文件
fs.writeFileSync('docs/public/rss.xml', feed.rss2())
fs.writeFileSync('docs/public/atom.xml', feed.atom1())
fs.writeFileSync('docs/public/feed.json', feed.json1())

console.log('✅ RSS 订阅源已生成')

构建时运行

json
// package.json
{
  "scripts": {
    "prebuild": "tsx scripts/generate-rss.ts"
  }
}

添加订阅链接

在 head 中添加

ts
// .vitepress/config.mts
export default defineConfig({
  head: [
    ['link', { rel: 'alternate', type: 'application/rss+xml', title: 'RSS', href: '/rss.xml' }],
    ['link', { rel: 'alternate', type: 'application/atom+xml', title: 'Atom', href: '/atom.xml' }],
    ['link', { rel: 'alternate', type: 'application/json', title: 'JSON Feed', href: '/feed.json' }]
  ]
})

订阅按钮组件

vue
<!-- .vitepress/theme/components/RssButton.vue -->
<script setup lang="ts">
const feeds = [
  { name: 'RSS', url: '/rss.xml', icon: 'rss' },
  { name: 'Atom', url: '/atom.xml', icon: 'atom' },
  { name: 'JSON', url: '/feed.json', icon: 'json' }
]
</script>

<template>
  <div class="rss-links">
    <a
      v-for="feed in feeds"
      :key="feed.name"
      :href="feed.url"
      class="rss-link"
      target="_blank"
    >
      <svg class="icon" viewBox="0 0 24 24" width="16" height="16">
        <path
          fill="currentColor"
          d="M6.18 15.64a2.18 2.18 0 0 1 2.18 2.18C8.36 19 7.38 20 6.18 20C5 20 4 19 4 17.82a2.18 2.18 0 0 1 2.18-2.18M4 4.44A15.56 15.56 0 0 1 19.56 20h-2.83A12.73 12.73 0 0 0 4 7.27V4.44m0 5.66a9.9 9.9 0 0 1 9.9 9.9h-2.83A7.07 7.07 0 0 0 4 12.93V10.1Z"
        />
      </svg>
      {{ feed.name }}
    </a>
  </div>
</template>

<style scoped>
.rss-links {
  display: flex;
  gap: 0.75rem;
}

.rss-link {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.5rem 1rem;
  background: var(--vp-c-bg-alt);
  border-radius: 8px;
  color: var(--vp-c-text-1);
  text-decoration: none;
  font-size: 0.875rem;
  transition: all 0.2s;
}

.rss-link:hover {
  background: var(--vp-c-brand-soft);
  color: var(--vp-c-brand-1);
}
</style>

完整文章内容

如果需要输出完整文章内容:

ts
import MarkdownIt from 'markdown-it'

const md = new MarkdownIt({
  html: true,
  linkify: true
})

// 在添加文章时
feed.addItem({
  // ...
  content: md.render(article.content),
  // 或者只输出纯文本
  // content: md.render(article.content).replace(/<[^>]*>/g, '')
})

参考链接

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献