Astro, of course, is built to use MDX and take full advantage of the MDX Content component, so Astro Discord member Chris Adiante proposed I simply use an Astro component to create the RSS (with full access to the MDX content) and then have it write the rss file to the file system. Since my site is Astro SSG (fully static, only changing when I rebuild the site) and not Astro SSR (server-rendered on demand), I can use this technique without any problems.1
By the way, Chris is the creator of the really amazing looking Astro M²DX remark plugins. If you’re using MDX with Astro, you should definitely give these a look!
In order to write an RSS feed using an Astro component, the Astro component has to get called – or to put it another way, it has to be used in a page. It can’t just sit in a folder in src. Also, it should be called once and once only. It can’t be put inside a layout file used by multiple pages because I want it written just once during the build. And finally, it has to have no effect on the rendering of the page that it’s in. Its purpose is to generate the RSS for all site posts and then write that RSS to a file called rss.xml. It has nothing that should be displayed, and it cannot alter the output of the page that hosts it.
To meet these requirements, I decided to use this Astro component in index.astro, the Astro page that gets built into index.html.
I use two components to create my RSS file, in the manner suggested by Chris Adiante: WriteFile.astro and RssXml.astro. RssXml.astro generates the RSS and WriteFile takes its output and dumps it to disk in the form a file called rss.xml.
The fact that Astro components write html should not be overlooked because it can also mess with the actual RSS XML generated, a truth that caused me much grief until I learned about Fragments and set:html. Using them in the combination
<Fragment set:html="" /> outputs RSS XML that isn’t messed with by Astro trying to ruin the format of XML link elements, for example.
This is not a complex component. All it has to do is generate and output XML. It doesn’t even have to get the posts to include, because those are passed in as a prop.
Some points of note:
- I create a constant for the top of the RSS file above the items which holds all the channel tags, and a constant for the end of the file after the items (which is just the closing channel and RSS tags).
- The most interesting detail to note by far, and indeed the whole reason behind this custom RSS approach, is the
<post.Content/>component inside the .map function. For each post being mapped, I have a
<Fragment/>wrapping everything before the post.Content object, then I end it, reference the post.Content object, and then create another Fragment object to wrap up the item XML.
It’s important to understand that
If you’re hoping for a giant code listing that requires lots of explanation, WriteFile.astro will make you sad. It’s ridiculously simple:
It’s 100% frontmatter. There is no output. If this has you thinking “but you just told me you have to have output in an Astro component”, that’s only true for Astro components that have to output any text. This component does not have to, it takes the text generated by RssXml.astro and writes it to disk. I understand completely if that explanation doesn’t clarify things much, but if you start playing with Astro components or pages, you’ll find out what happens when you don’t write html tags or include other Astro components in the base output.2
The important point with this one is waiting for that slot to render before performing fs.writeFile. I never would have known to do this, or especially how to do this. This was all Chris Adiante. The reason it’s necessary to do this is because the page needs to be written up to that point for the RssXml.astro component to do its thing and have something for WriteFile.astro to actually write to disk.
The one thing I did differently to his suggestion is to not look and wait for default slot. Using default slot meant everything before RssXml.astro’s output also got written into the file. Not what I want. As a result, I created a named slot in my Base.astro layout template (which index.astro uses) and then target both WriteFile.astro and RssXml.astro to this slot inside of index.astro.
index.astro content section
This is just the content section of index.astro without any frontmatter, just to show you how I incorporated my WriteFile and RssXml Astro components so that they do their thing when the index page is built.
The above is everything in index.astro except the frontmatter section. The part that writes the RSS file is at the bottom. One thing about named slots that tripped me up is that both components have to name the slot to use explicitly, not just the outer component. Anything you want to wind up in a named slot needs to name that slot.
The final piece of the puzzle is just the named slot at the bottom of Base.astro, my layout template used by index.astro and all my other pages.
See that innocent looking slot down at almost the very bottom named “rss-writer”? That’s the named slot that allows index.astro to render the WriteFile and RssXml astro components. You might also notice the default
<slot/>in between the html
<main> tags. That’s where all the content generated by any page that uses Base.astro as its layout gets rendered.
TLDR; and Summary
There’s a lot to digest here. The key takeaways are:
- As a result, trying to access that content for a full-item-body RSS feed requires using Astro components to generate the feed.
- Astro components only write HTML, not XML or an RSS feed file. Using Astro components to generate XML therefore means writing that to disk yourself.
- This only works in SSG environments. You would not want to use this with an SSR site because it would write rss.xml over and over again. If I were going to use this on an SSR site, I’d want a page with Astro components that isn’t public facing, which would get called by a node script executed by a cron job or whenever a post is added to the site.
As always, I’m not a brilliant programmer and I’m sure there are better ways of doing everything I did. I also would never have figured out the syntax and layout requirements for this without the help of Chris in the Astro Discord.