Skip to content
On this pageCreating the API Route
  1. Creating the API Route
  2. Auto-Discovery in the HTML Head
  3. Caching and Invalidation
  4. Validating the Feed

Set Up an RSS Feed on Your EmDash Site

Ben 2 min read

RSS isn't dead. Feed readers have a loyal audience, and offering a feed is a signal that your site is serious about distribution. On an EmDash site, it takes one file — an Astro API route that queries your content and returns valid RSS 2.0 XML.

I'll show you the exact implementation I use on this site: fetches the latest learn articles, generates standards-compliant XML with an Atom self-link, sets proper cache headers, and auto-discovers from the HTML head. No npm package needed.

Creating the API Route

Create src/pages/rss.xml.ts. Astro treats any file in pages/ with a GET export as a server endpoint. The .xml.ts extension tells Astro what content type to expect, but you still need to set the Content-Type header explicitly in your response.

The route uses getEmDashCollection to fetch the 20 most recent learn articles in descending order. Always call Astro.cache.set(cacheHint) — this hooks into EmDash's cache invalidation so the feed refreshes automatically when you publish new content or update existing entries.

src/pages/rss.xml.ts
import type { APIRoute } from 'astro';
import { getEmDashCollection } from 'emdash';
import { getSiteSettings } from 'emdash';

export const GET: APIRoute = async ({ site, cache }) => {
  const settings = await getSiteSettings();
  const { entries, cacheHint } = await getEmDashCollection('learn', {
    limit: 20,
    orderBy: { published_at: 'desc' },
    status: 'published',
  });

  // Hook into EmDash cache invalidation
  cache.set(cacheHint);

  const siteUrl = site?.toString() ?? 'https://dashstro.com';
  const feedUrl = `${siteUrl}rss.xml`;

  const items = entries
    .filter(entry => entry.data.title && entry.publishedAt)
    .map(entry => {
      const url = `${siteUrl}learn/${entry.id}`;
      const pubDate = new Date(entry.publishedAt!).toUTCString();
      const description = entry.data.excerpt
        ? `<![CDATA[${entry.data.excerpt}]]>`
        : '';

      return `
    <item>
      <title><![CDATA[${entry.data.title}]]></title>
      <link>${url}</link>
      <guid isPermaLink="true">${url}</guid>
      <pubDate>${pubDate}</pubDate>
      <description>${description}</description>
    </item>`;
    })
    .join('');

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title><![CDATA[${settings.title}]]></title>
    <link>${siteUrl}</link>
    <description><![CDATA[${settings.tagline ?? ''}]]></description>
    <language>en</language>
    <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
    <atom:link href="${feedUrl}" rel="self" type="application/rss+xml" />
    ${items}
  </channel>
</rss>`;

  return new Response(xml, {
    headers: {
      'Content-Type': 'application/rss+xml; charset=utf-8',
      'Cache-Control': 's-maxage=3600, stale-while-revalidate=86400',
    },
  });
};

Auto-Discovery in the HTML Head

Feed readers discover RSS feeds by scanning for a link element in the page head. Add this to your Base layout, inside the head, after EmDashHead. The type application/rss+xml is what triggers auto-detection in browsers and feed reader browser extensions.

One link element in the head and every feed reader on the planet can find your content automatically. It costs nothing to add and pays off every time someone subscribes.
src/layouts/Base.astro (head section)
<head>
  <EmDashHead pageContext={pageContext} />

  <!-- RSS auto-discovery: enables feed readers to detect the feed -->
  <link
    rel="alternate"
    type="application/rss+xml"
    title="Dashstro RSS Feed"
    href="/rss.xml"
  />
</head>

Caching and Invalidation

The Cache-Control header in the response sets a one-hour edge cache (s-maxage=3600) with a 24-hour stale-while-revalidate window. This means Cloudflare serves the cached feed for up to 24 hours while revalidating in the background, but the cached version is considered fresh for one hour.

The Astro.cache.set(cacheHint) call integrates with EmDash's content cache. When you publish or update an article in the admin panel, EmDash purges the cache keys associated with that content — including any pages that queried the learn collection. The RSS feed gets invalidated automatically on publish.

Validating the Feed

Run the dev server and hit http://localhost:4321/rss.xml. You should get valid XML with your articles. Paste the URL into the W3C Feed Validator at validator.w3.org/feed/ to verify it passes RSS 2.0 validation. Common issues: missing pubDate on entries (filter them out if publishedAt is null), special characters not wrapped in CDATA, and missing the Atom self-link.

Next: Build your first EmDash website