DEV Community

loading...

How to make an RSS feed in SvelteKit

David Parker
Tutorials: https://www.programmingtil.com/ YouTube Tutorials and More: https://www.youtube.com/user/iamdavidwparker Personal Blog: https://www.davidwparker.com/
Originally published at davidwparker.com ・3 min read

Chances are, if you're consuming a lot of content, you're not checking a ton of individual sites.
You may be checking something like Reddit, or another aggregator, or possibly one of the bigger blogging platforms nowadays (dev.to, medium, etc). But that still leaves out large portions of the Internet.

If you control your own website and channel, and you're using SvelteKit, then you'll likely want an RSS feed so that your end users can subscribe to your content in their favorite feed reader.

So, what's it take to to it with SvelteKit? Not a lot!

Note: if you'd rather watch a video tutorial on how to implement an RSS feed, you can check out my YouTube video here.

Here's the complete code for this blog's rss feed:

routes/rss.js

export const get = async () => {
  const res = await fetch(import.meta.env.VITE_BASE_ENDPOINT + '/posts/posts.json');
  const data = await res.json();
  const body = render(data.posts);
  const headers = {
    'Cache-Control': `max-age=0, s-max-age=${600}`,
    'Content-Type': 'application/xml',
  };
  return {
    body,
    headers,
  };
};

const render = (posts) => `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="http://wwww.davidwparker.com/rss" rel="self" type="application/rss+xml" />
<title>David W Parker</title>
<link>https://www.davidwparker.com</link>
<description>David W Parker's blog about Code, Entrepreneurship, and more</description>
${posts
  .map(
    (post) => `<item>
<guid>https://www.davidwparker.com/posts/${post.slug}</guid>
<title>${post.title}</title>
<link>https://www.davidwparker.com/posts/${post.slug}</link>
<description>${post.description}</description>
<pubDate>${new Date(post.published).toUTCString()}</pubDate>
</item>`
  )
  .join('')}
</channel>
</rss>
`;
Enter fullscreen mode Exit fullscreen mode

Let's break it down

The endpoint

// GET /rss
export const get = async () => {
  const res = await fetch(import.meta.env.VITE_BASE_ENDPOINT + '/posts/posts.json');
  const data = await res.json();
  const body = render(data.posts);
  const headers = {
    'Cache-Control': `max-age=0, s-max-age=${600}`,
    'Content-Type': 'application/xml',
  };
  return {
    body,
    headers,
  };
};
Enter fullscreen mode Exit fullscreen mode

This is a get request that lives at /rss. In it, I make a simple request to /posts/posts.json to get all the blog
articles that I want for this RSS feed.
I call res.json() to get the resulting json, then send the posts within that json to the render method to build my body.
Once I get the body, I set a few headers, and return the resulting body and header which is needed for the SvelteKit endpoint.

The body

const render = (posts) => `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="http://wwww.davidwparker.com/rss" rel="self" type="application/rss+xml" />
<title>David W Parker</title>
<link>https://www.davidwparker.com</link>
<description>David W Parker's blog about Code, Entrepreneurship, and more</description>
${posts
  .map(
    (post) => `<item>
<guid>https://www.davidwparker.com/posts/${post.slug}</guid>
<title>${post.title}</title>
<link>https://www.davidwparker.com/posts/${post.slug}</link>
<description>${post.description}</description>
<pubDate>${new Date(post.published).toUTCString()}</pubDate>
</item>`
  )
  .join('')}
</channel>
</rss>
`;
Enter fullscreen mode Exit fullscreen mode

We start by making our xml declaration and using the proper rss tag with the definition from w3.org.
From there, it's just a standard rss feed, which you can find from anywhere on the Internet.

In my example, I have a channel, with atom:link which references itself. Inside, I have a title for my feed/site, and a description. From there, I map each of my resulting posts into their own <item> tag along with their own guid, title, link, description, and pubDate. Close out the tags, and we're done.

posts.json

This is less important, but it's just another get endpoint that returns a bunch of posts from imported md files.
At this point, there's a bunch of examples of this all around the Internet- but here's mine just in case you haven't seen it yet:

// GET /posts/posts.json
export const get = async ({ query }) => {
  let posts = await Promise.all(
    Object.entries(import.meta.glob('./*.md')).map(async ([path, page]) => {
      const { metadata } = await page();
      const slug = path.split('/').pop().split('.').shift();
      return { ...metadata, slug };
    })
  );
  if (query.get('q') !== null) {
    posts = posts.reduce((accum, val) => {
      if (val.categories.includes(query.get('q'))) {
        accum.push(val);
      }
      return accum;
    }, []);
  }
  posts.sort((a, b) => (a.published > b.published ? -1 : 1));

  return {
    status: 200,
    body: { posts },
  };
};
Enter fullscreen mode Exit fullscreen mode

Discussion (0)