---
url: /en/guide/features/feed/index.md
---
## Overview

The theme can generate **RSS 2.0**, **Atom 1.0** and **JSON Feed 1.1** files during a production build,
and inject `<link rel="alternate">` into the page `<head>` so that readers and automation tools can
discover the subscription URLs automatically.

Unlike the common "one feed per site" setup, feeds are aligned with the `collections` model and support
splitting channels by **locale × collection**:

:::: file-tree

* .vuepress/dist
  * feed.xml
  * atom.xml
  * feed.json
  * blog
    * feed.xml
  * notes
    * feed.xml
  * en
    * feed.xml
    * blog
      * feed.xml
        ::::

## Quick Start

Configure `hostname` and `feed`:

```ts title=".vuepress/config.ts"
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'

export default defineUserConfig({
  theme: plumeTheme({
    hostname: 'https://example.com',
    feed: true, // only outputs /feed.xml
  }),
})
```

:::: warning `hostname` is required
Every link in a feed must be absolute. Without `hostname` the theme skips generation and logs a warning.
`base` must also match the deployment path of the site, otherwise subscription URLs point to the wrong place.
::::

:::: warning Production build only
Feeds are generated in the `onGenerated` hook, i.e. only during `vuepress build`.
The dev server (`vuepress dev`) generates no feed.
::::

## Multiple Collections and Locales

### Collection channels

Only the global channel is generated by default. Sites with multiple collections can enable
`perCollection` to emit a dedicated channel for every eligible collection:

```ts
export default defineUserConfig({
  theme: plumeTheme({
    hostname: 'https://example.com',
    feed: {
      perCollection: true,
      formats: ['rss', 'atom', 'json'],
    },
    locales: {
      '/': {
        collections: [
          { type: 'post', dir: 'blog', title: 'Blog', link: '/blog/' },
          { type: 'post', dir: 'notes', title: 'Notes', link: '/notes/' },
        ],
      },
    },
  }),
})
```

Output:

| Channel | File | Public URL |
| --- | --- | --- |
| Global | `dist/feed.xml` | `https://example.com/feed.xml` |
| Blog | `dist/blog/feed.xml` | `https://example.com/blog/feed.xml` |
| Notes | `dist/notes/feed.xml` | `https://example.com/notes/feed.xml` |

An article may appear in both the global channel and a collection channel. This is expected.

### Collection level switches

`feed` on a collection controls whether it is included and whether it owns a dedicated channel:

```ts
// `doc` collections are excluded by default, `feed: true` opts in explicitly
const doc = { type: 'doc', dir: 'guide', title: 'Guide', sidebar: 'auto', feed: true }
```

```ts
// Disable subscriptions for a collection
const post = { type: 'post', dir: 'notes', title: 'Notes', feed: false }
```

`feed: true` is equivalent to `feed: {}`: the collection is included in feeds and gets a dedicated
channel, without being limited by the theme level `perCollection` / `includeDocCollections`.

See [collection options](../../config/collections.md#collection-level-feed-options) for every field.

### Locales

Each locale outputs at least one global channel, and the output path follows the locale prefix:

```
dist/feed.xml        → https://example.com/feed.xml
dist/en/feed.xml     → https://example.com/en/feed.xml
```

`channel.link` and `collection.feed.link` are **locale relative** (e.g. `/blog/`).
The theme prepends the current locale prefix automatically, so do not write `/en/` yourself.

### Custom filenames and directories

```ts
export default defineUserConfig({
  theme: plumeTheme({
    feed: {
      filename: {
        rss: 'rss.xml',
        atom: 'atom.xml',
        json: 'feed.json',
      },
      channel: {
        link: '/subscribe/', // writes the global channel to dist/subscribe/
        title: 'Subscribe',
        author: { name: 'pengzhanbo', email: 'i@example.com' },
        ttl: 60,
      },
    }
  })
})
```

## Full Content and Size Trade-offs

`content` defaults to `'excerpt'`, which only outputs the summary. Set it to `'full'` to output the
rendered HTML of the page.

:::: warning Vue components are not rendered in full mode
The feed content comes from `page.contentRendered` at build time, where Vue components provided by
the theme and `vuepress-plugin-md-power` (`<VPBadge>`, `<VPCard>`, REPL, charts…) are not yet parsed
by a browser. Their tags are stripped and only their inner text is kept. Readers should visit the
original page for interactive content.
::::

Full mode increases file size significantly. A single channel larger than 10 MB triggers a warning,
and you should switch to `content: 'excerpt'` or lower `limit`.

## Security and Privacy

The following pages and content **never** enter any feed:

| Case | Behaviour |
| --- | --- |
| Page with `password` | Excluded |
| Matched by `encrypt.rules` | Excluded |
| `encrypt.global` | The whole feature is disabled with a warning |
| `frontmatter.draft: true` | Excluded |
| `frontmatter.article: false` | Excluded |
| `frontmatter.feed: false` | Excluded (highest priority) |
| Home / post list / tag pages and other non-content pages | Excluded |
| `::: encrypt` snippet | The corresponding tag is removed from the content |

Exclude a single page:

```yaml
---
title: Internal draft ideas
feed: false
---
```

Override the metadata of a single entry:

```yaml
---
title: Article title
feed:
  title: Title shown in the reader
  summary: Custom summary
  guid: /custom-id/
---
```

## Auto Discovery

During the build, every page that belongs to a generated channel receives the matching link in `<head>`:

```html
<link rel="alternate" type="application/rss+xml" title="Blog" href="https://example.com/blog/feed.xml">
```

* One link per enabled format;
* Nothing is injected when the page already declares an `alternate` link of the same type;
* Set `linkTag: false` to disable it.

Readers only need to paste the site domain into Feedly / Inoreader / NetNewsWire to discover the feeds.

## Validation

* RSS / Atom: <https://validator.w3.org/feed/>
* JSON Feed: <https://validator.jsonfeed.org/>

Verify determinism locally:

```bash
pnpm docs:build
shasum dist/feed.xml dist/atom.xml dist/feed.json
# build again and compare, the results must be identical
```

## FAQ

::: collapse

* `hostname` warning and no files generated

  Make sure `hostname` is a valid site origin (e.g. `https://example.com`).
  If the site is deployed under a sub path, use `base` (e.g. `/docs/`) instead of appending it to `hostname`.

* Two collections overwrite each other

  The output path is the channel home route plus the filename. Two collections sharing the same `link`
  conflict: the first channel wins and a warning is logged. Use `collections[].feed.link` to give them
  different channel links.

* Feed dates differ from the site

  Feeds use "local time + timezone offset", e.g. `2025-06-10T08:00:00+08:00`. Building in a different
  timezone produces a different string for the same instant. CI sets `TZ=Etc/UTC`, so artifacts are reproducible.

* Some articles are missing from the feed

  A feed entry needs a date: `frontmatter.createTime` → `frontmatter.date` → `git.createdTime` →
  `git.updatedTime`. Pages without any date are skipped and counted in the build log.
  Also check whether the page is encrypted, a draft, or marked with `article: false` / `feed: false`.

* No feed in dev mode

  `onGenerated` only runs during a production build. Run `vuepress build` and preview the output with a
  static server.

:::
