DEV Community

David Parker
David Parker

Posted on β€’ Originally published at davidwparker.com

1

How to make an RSS feed in SvelteKit

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
πŸ‘‹ While you are here

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

πŸ‘‹ Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay