Hey there! My blog no longer uses remix, and this post only applies to Remix 1, and I ๐Ÿคž hope you've upgraded.

One of the reasons I chose Remix for this site was the ability to add server-side code easily to any route, or even to have no react components returned at all. It kind of reminds me of PHP, or rails! And so Iโ€™m able to add a RSS feed to this blog without having to do much extra work.

Resource Route

Remix has a feature called resource routes that let you create a page that only responds from the server with data, not with a React component. Perfect for returning JSON, or in this case, an RSS feed.

To create a resource route, you just need to create a file in the app/routes directory. The file name will be the route, and the file contents will be the data that gets returned. Iโ€™ve created a file at app/routes/[rss.xml].tsx that will return a RSS feed of my posts.

The reason for the `[` and `]` around the file name is because remix treats a file name with a period `.` into slashes `/`. So if the filename were `rss.xml.tsx`, the route would be `/rss/xml` instead of the `/rss.xml` we're looking for.
// app/routes/[rss.xml].tsx
export type RssPost = {
  title: string;
  link: string;
  description: string;
  pubDate: string;
  guid?: string;
  author?: string;
};

/**
 * Generates an RSS feed from a list of posts.
 * @param title Title of the RSS feed
 * @param description A description of the RSS feed
 * @param link Link to the main page for the RSS feed
 * @param posts List of posts to include in the feed
 */
export function generateRss({
  description,
  posts,
  link,
  title,
}: {
  title: string;
  description: string;
  link: string;
  posts: RssPost[];
}): string {
  const rssHeader = `<?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <title>${title}</title>
        <description>${description}</description>
        <link>${link}</link>
        <language>en-us</language>
        <ttl>60</ttl>
        <atom:link href="https://keith.is/rss.xml" rel="self" type="application/rss+xml" />`;

  const rssBody = posts
    .map(
      (post) => `
          <item>
            <title><![CDATA[${post.title}]]></title>
            <description><![CDATA[${post.description}]]></description>
            <pubDate>${post.pubDate}</pubDate>
            <link>${post.link}</link>
            <guid isPermaLink="false">${post.link}</guid>
          </item>`,
    )
    .join("");

  const rssFooter = `
      </channel>
    </rss>`;

  return rssHeader + rssBody + rssFooter;
}

The generateRss function takes a title, description, link, and list of posts and returns a string of the RSS feed. In the loader function, weโ€™ll use this function to generate the RSS feed.

// app/routes/[rss.xml].tsx
export const loader: LoaderFunction = async () => {
  const posts = await getPosts();
  const feed = generateRss({
    title: "Keith's Blog",
    description: "A blog about web development and other things.",
    link: "https://keith.is",
    posts: posts.map((post) => ({
      title: post.title,
      link: `https://keith.is/post/${post.slug}`,
      description: post.excerpt,
      pubDate: new Date(post.date).toUTCString(),
    })),
  });

  return new Response(feed, {
    headers: {
      "Content-Type": "application/xml",
      "Cache-Control": "public, max-age=2419200",
    },
  });
};
  • getPosts() is my function for getting posts from markdown, but you can replace that with any method, like connecting to an API with fetch
  • The headers ensure that the page is XML, and that itโ€™s cached for 4 weeks

If you browse to /rss.xml, youโ€™ll see your brand new RSS feed โœจ! You can find mine here. It ends up looking like:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Keith's Blog</title>
    <description>A blog about web development and other things.</description>
    <link>https://keith.is</link>
    <language>en-us</language>
    <ttl>60</ttl>
    <atom:link href="https://keith.is/rss.xml" rel="self" type="application/rss+xml" />
      <item>
        <title><![CDATA[Remix RSS feed]]></title>
        <description><![CDATA[Building this site, I had to add a RSS feed for my posts. Using remix resource routes, it's a snap.]]></description>
        <pubDate>Thu, 12 Jan 2023 00:00:00 GMT</pubDate>
        <link>https://keith.is/post/remix-rss</link>
        <guid isPermaLink="false">https://keith.is/post/remix-rss</guid>
      </item>
      <item>
        <title><![CDATA[New year, new site]]></title>
        <description><![CDATA[Creating a new site for 2023, focused on writing more and simplifying the process of getting content up. and bigger emojis.]]></description>
        <pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate>
        <link>https://keith.is/post/new-year-new-site-part-20</link>
        <guid isPermaLink="false">https://keith.is/post/new-year-new-site-part-20</guid>
      </item>
  </channel>
</rss>

Make sure to add the link to your RSS feed to your site. I added it to the footer of my site, and more importantly as a link tag.

// app/root.tsx
export const links: LinksFunction = () => {
  return [{ rel: "alternate", type: "application/rss+xml", href: "/rss.xml" }];
};