RSS & Atom Feeds
Relevant source files
The following files were used as context for generating this wiki page:The Charlotte project provides dynamically generated XML feeds for both RSS and Atom formats, allowing users to subscribe to blog updates using feed readers. These feeds are generated from the site's post content and configured metadata. The implementation leverages Astro's server-side rendering capabilities to create API endpoints at /rss.xml and /atom.xml.
The system fetches all non-draft posts, sorts them by publication date, and then processes their content to be compliant with XML standards. This includes converting Markdown to HTML, sanitizing the output, and ensuring all URLs are absolute.
Core Functionality
The feed generation is handled by two distinct Astro endpoints, each using a specialized library to format the XML output.
Sources: src/pages/rss.xml.ts, src/pages/atom.xml.ts, package.json
Endpoints
/rss.xml: Generates a standard RSS 2.0 feed./atom.xml: Generates a standard Atom 1.0 feed.
Both endpoints are implemented as GET handlers within Astro's src/pages/ directory.
Sources: src/pages/rss.xml.ts:25, src/pages/atom.xml.ts:21
Key Dependencies
The following table lists the primary npm packages used to build the feeds.
| Package | Version | Purpose |
|---|---|---|
@astrojs/rss | ^4.0.12 | Core library for generating RSS 2.0 feeds in Astro. |
astrojs-atom | ^1.0.144 | Library for generating Atom 1.0 feeds. |
markdown-it | ^14.1.0 | Used to parse and render Markdown post content into HTML. |
sanitize-html | ^2.17.0 | Cleans the rendered HTML to prevent invalid markup and remove potentially malicious code. |
Sources: package.json:28,45,46,55, src/pages/rss.xml.ts:1,5,6, src/pages/atom.xml.ts:2,3,4
Feed Generation Flow
The process for generating both RSS and Atom feeds follows a similar sequence of operations. It begins with an HTTP request to the endpoint and concludes with an XML response.
The diagram below illustrates the common data flow for generating a feed.
Process Steps:
- Fetch & Sort Posts: The
getSortedPosts()function fromcontent-utils.tsis called to retrieve all blog posts that are not marked as drafts. The posts are sorted in descending order by theirpublisheddate. - Content Processing Loop: The system iterates through each post.
- Strip Invalid XML Characters: The
stripInvalidXmlCharsutility function removes control characters that are invalid in XML documents to prevent parsing errors. - Render Markdown: The post's Markdown body is converted to an HTML string using
markdown-it. - Make URLs Absolute (RSS only): For the RSS feed, a regular expression is used to find relative
srcandhrefattributes (e.g.,src="/image.png") and convert them to absolute URLs using the site's base URL fromsiteConfig. - Sanitize HTML: The resulting HTML is sanitized using
sanitize-htmlto ensure it is well-formed. The default allowed tags are extended to include<img>tags. - Map Data: The post's metadata (title, date, description) and the processed content are mapped to the structure required by the respective feed library (
@astrojs/rssorastrojs-atom). - Generate XML: Once all posts are processed, the main feed generation function (
rss()orgetAtomResponse()) is called with the site-level configuration and the array of feed items to produce the final XML string.
Sources: src/pages/rss.xml.ts:9-15, 26, 34-58, src/pages/atom.xml.ts:8-14, 23, 41-59, src/utils/content-utils.ts:74-84
RSS Feed Details (/rss.xml)
The RSS feed is generated using the @astrojs/rss package. It includes custom data for language and a self-referencing atom link.
Sources: src/pages/rss.xml.ts
Data Mapping
Each blog post is mapped to an <item> in the RSS feed with the following properties:
| RSS Field | Source Data | Notes |
|---|---|---|
title | post.data.title | The title of the blog post. |
pubDate | post.data.published | The publication date of the post. |
description | post.data.description | The post's short description. |
link | /posts/{post.slug}/ | A generated absolute URL to the post. |
content | post.body | The full post content, processed as HTML. |
Sources: src/pages/rss.xml.ts:43-58
Content Processing
A key step unique to the RSS generation is the explicit conversion of relative URLs within the rendered HTML to absolute URLs. This is done before sanitization to ensure links and images in the feed content are resolved correctly.
// src/pages/rss.xml.ts:40-42
const absoluteContent = renderedContent.replace(
/(src|href)="(\/[^"]+)"/g, // Find src="/..." or href="/..."
(_, attr, relativePath) => `${attr}="${absoluteUrl(relativePath)}"`,
);
Sources: src/pages/rss.xml.ts:38-42
Custom RSS Data
The RSS feed includes additional metadata in the <channel> element via the customData option.
<language>: Set fromsiteConfig.lang, with underscores replaced by hyphens (e.g.,zh_CNbecomeszh-CN).<atom:link>: A self-referencing link for feed discovery, as per the Atom namespace specification.
Sources: src/pages/rss.xml.ts:61-68
Atom Feed Details (/atom.xml)
The Atom feed is generated using the astrojs-atom package and follows the Atom 1.0 specification.
Sources: src/pages/atom.xml.ts
Data Mapping
Each blog post is mapped to an <entry> in the Atom feed with the following properties:
| Atom Field | Source Data | Notes |
|---|---|---|
title | post.data.title | The title of the blog post. |
id | Absolute URL to post | A unique identifier, which is the post's absolute URL. |
updated | post.data.updated or post.data.published | The date the post was last updated or published. |
published | post.data.published | The original publication date. |
author | profileConfig.name | The site author's name from the configuration. |
content | post.body | The full post content, processed as HTML. |
summary | post.data.description | The post's short description. |
link | Absolute URL to post | An alternate link to the HTML version of the post. |
Sources: src/pages/atom.xml.ts:40-62, src/config.ts:114
Unlike the RSS feed, the Atom feed generation logic does not explicitly convert relative image paths to absolute ones. It relies on the sanitizeHtml and parser.render output directly.
Sources: src/pages/atom.xml.ts:51-55
Data Sources
The content for the feeds is aggregated from two primary sources within the project: the post content collection and the global site configuration.
Post Content
All blog posts are stored in src/content/posts/ and managed by Astro's content collections. The schema for posts defines the fields used in the feeds.
| Field | Type | Used In Feeds |
|---|---|---|
title | string | Yes |
published | date | Yes |
updated | date (optional) | Yes (Atom feed) |
draft | boolean (optional) | Yes (to filter out drafts) |
description | string (optional) | Yes |
body | (implicit) string | Yes (as content) |
Sources: src/content/config.ts:4-10, src/utils/content-utils.ts:75-76
Site Configuration
Global metadata for the feeds is sourced from src/config.ts.
siteConfig Property | Type | Usage |
|---|---|---|
title | string | The main title of the RSS and Atom feeds. |
subtitle | string | Used as a fallback for the feed description. |
url | string | The base URL for constructing absolute links. |
lang | string | Sets the language of the feed (<language> tag). |
description | string | The primary description for the feed. |
Sources: src/config.ts:11-17, src/pages/rss.xml.ts:29-32, src/pages/atom.xml.ts:25-26, 73-76