{"id":430,"date":"2024-10-07T13:00:00","date_gmt":"2024-10-07T13:00:00","guid":{"rendered":"https:\/\/fdswebdesign.com\/?p=430"},"modified":"2024-10-15T23:32:28","modified_gmt":"2024-10-15T23:32:28","slug":"build-a-static-rss-reader-to-fight-your-inner-fomo","status":"publish","type":"post","link":"https:\/\/fdswebdesign.com\/index.php\/2024\/10\/07\/build-a-static-rss-reader-to-fight-your-inner-fomo\/","title":{"rendered":"Build A Static RSS Reader To Fight Your Inner FOMO"},"content":{"rendered":"

Build A Static RSS Reader To Fight Your Inner FOMO<\/title><\/p>\n<article>\n<header>\n<h1>Build A Static RSS Reader To Fight Your Inner FOMO<\/h1>\n<address>Karin Hendrikse<\/address>\n<p> 2024-10-07T13:00:00+00:00<br \/>\n 2024-10-15T23:05:45+00:00<br \/>\n <\/header>\n<p>In a fast-paced industry like tech, it can be hard to deal with the fear of missing out on important news. But, as many of us know, there\u2019s an absolutely huge amount of information coming in daily, and finding the right time and balance to keep up can be difficult, if not stressful. A classic piece of technology like <strong>an RSS feed is a delightful way of taking back ownership of our own time<\/strong>. In this article, we will create a static Really Simple Syndication (RSS) reader that will bring you the latest curated news only once (yes: <em>once<\/em>) a day.<\/p>\n<p>We\u2019ll obviously work with RSS technology in the process, but we\u2019re also going to combine it with some things that maybe you haven\u2019t tried before, including <strong>Astro<\/strong> (the static site framework), <strong>TypeScript<\/strong> (for JavaScript goodies), a package called <strong>rss-parser<\/strong> (for connecting things together), as well as <strong>scheduled functions<\/strong> and <strong>build hooks<\/strong> provided by Netlify (although there are other services that do this).<\/p>\n<p>I chose these technologies purely because I really, really enjoy them! There may be other solutions out there that are more performant, come with more features, or are simply more comfortable to you — and in those cases, I encourage you to swap in whatever you\u2019d like. The most important thing is getting the end result!<\/p>\n<h2 id=\"the-plan\">The Plan<\/h2>\n<p>Here\u2019s how this will go. Astro generates the website. I made the intentional decision to use a static site because I want the different RSS feeds to be fetched only once during build time, and that\u2019s something we can control each time the site is \u201crebuilt\u201d and redeployed with updates. That\u2019s where Netlify\u2019s scheduled functions come into play, as they let us trigger rebuilds automatically at specific times. There is no need to manually check for updates and deploy them! Cron jobs can just as readily do this if you prefer a server-side solution.<\/p>\n<p>During the triggered rebuild, we\u2019ll let the rss-parser package do exactly what it says it does: parse a list of RSS feeds that are contained in an array. The package also allows us to set a filter for the fetched results so that we only get ones from the past day, week, and so on. Personally, I only render the news from the last seven days to prevent content overload. We\u2019ll get there!<\/p>\n<p>But first…<\/p>\n<h2 id=\"what-is-rss\">What Is RSS?<\/h2>\n<p>RSS is a web feed technology that you can feed into a reader or news aggregator. Because RSS is standardized, you know what to expect when it comes to the feed\u2019s format. That means we have a ton of fun possibilities when it comes to handling the data that the feed provides. Most news websites have their own RSS feed that you can subscribe to (this is <strong>Smashing Magazine\u2019s RSS feed<\/strong>: <a href=\"https:\/\/www.smashingmagazine.com\/feed\/\">https:\/\/www.smashingmagazine.com\/feed\/<\/a>). An RSS feed is capable of updating every time a site publishes new content, which means it can be a quick source of the latest news, but we can tailor that frequency as well.<\/p>\n<p>RSS feeds are written in an Extensible Markup Language (XML) format and have specific elements that can be used within it. Instead of focusing too much on the technicalities here, I\u2019ll give you a link to the <a href=\"https:\/\/www.rssboard.org\/rss-specification\">RSS specification<\/a>. Don\u2019t worry; that page should be scannable enough for you to find the most pertinent information you need, like the kinds of elements that are supported and what they represent. For this tutorial, we\u2019re only using the following elements: <strong><code><title><\/code><\/strong>, <strong><code><link><\/code><\/strong>, <strong><code><description><\/code><\/strong>, <strong><code><item><\/code><\/strong>, and <strong><code><pubDate><\/code><\/strong>. We\u2019ll also let our RSS parser package do some of the work for us.<\/p>\n<div data-audience=\"non-subscriber\" data-remove=\"true\" class=\"feature-panel-container\">\n<aside class=\"feature-panel\">\n<div class=\"feature-panel-left-col\">\n<div class=\"feature-panel-description\">\n<p>Meet <strong><a data-instant href=\"\/printed-books\/touch-design-for-mobile-interfaces\/\">Touch Design for Mobile Interfaces<\/a><\/strong>, Steven Hoober\u2019s brand-new guide on <strong>designing for mobile<\/strong> with proven, universal, human-centric guidelines. <strong>400 pages<\/strong>, jam-packed with in-depth user research and <strong>best practices<\/strong>.<\/p>\n<p><a data-instant href=\"https:\/\/www.smashingmagazine.com\/printed-books\/touch-design-for-mobile-interfaces\/\" class=\"btn btn--green btn--large\">Jump to table of contents \u21ac<\/a><\/div>\n<\/div>\n<div class=\"feature-panel-right-col\"><a data-instant href=\"https:\/\/www.smashingmagazine.com\/printed-books\/touch-design-for-mobile-interfaces\/\" class=\"feature-panel-image-link\"><\/p>\n<div class=\"feature-panel-image\">\n<img decoding=\"async\" loading=\"lazy\" class=\"feature-panel-image-img lazyload\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Feature Panel\" width=\"480\" height=\"697\" data-src=\"https:\/\/archive.smashing.media\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b14658fc-bb2d-41a6-8d1a-70eaaf1b8ec8\/touch-design-book-shop-opt.png\"><\/p>\n<\/div>\n<p><\/a>\n<\/div>\n<\/aside>\n<\/div>\n<h2 id=\"creating-the-state-site\">Creating The State Site<\/h2>\n<p>We\u2019ll start by creating our Astro site! In your terminal run <code>pnpm create astro@latest<\/code>. You can use any package manager you want — I\u2019m simply trying out <a href=\"https:\/\/pnpm.io\">pnpm<\/a> for myself.<\/p>\n<p>After running the command, Astro\u2019s chat-based helper, Houston, walks through some setup questions to get things started.<\/p>\n<pre><code class=\"language-bash\"> astro Launch sequence initiated.\n\n dir Where should we create your new project?\n .\/rss-buddy\n\n tmpl How would you like to start your new project?\n Include sample files\n\n ts Do you plan to write TypeScript?\n Yes\n\n use How strict should TypeScript be?\n Strict\n\n deps Install dependencies?\n Yes\n\n git Initialize a new git repository?\n Yes\n<\/code><\/pre>\n<p>I like to use Astro\u2019s sample files so I can get started quickly, but we\u2019re going to clean them up a bit in the process. Let\u2019s clean up the <code>src\/pages\/index.astro<\/code> file by removing everything inside of the <code><main><\/main><\/code> tags. Then we\u2019re good to go!<\/p>\n<p>From there, we can spin things by running <code>pnpm start<\/code>. Your terminal will tell you which localhost address you can find your site at.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"pulling-information-from-rss-feeds\">Pulling Information From RSS feeds<\/h2>\n<p>The <code>src\/pages\/index.astro<\/code> file is where we will make an array of RSS feeds we want to follow. We will be using <a href=\"https:\/\/docs.astro.build\/en\/basics\/astro-syntax\/\">Astro\u2019s template syntax<\/a>, so between the two code fences (—), create an array of <code>feedSources<\/code> and add some feeds. If you need inspiration, you can copy this:<\/p>\n<pre><code class=\"language-javascript\">const feedSources = [\n 'https:\/\/www.smashingmagazine.com\/feed\/',\n 'https:\/\/developer.mozilla.org\/en-US\/blog\/rss.xml',\n \/\/ etc.\n]\n<\/code><\/pre>\n<p>Now we\u2019ll install the <a href=\"https:\/\/github.com\/rbren\/rss-parser\">rss-parser package<\/a> in our project by running <code>pnpm install rss-parser<\/code>. This package is a small library that turns the XML that we get from fetching an RSS feed into JavaScript objects. This makes it easy for us to read our RSS feeds and manipulate the data any way we want.<\/p>\n<p>Once the package is installed, open the <code>src\/pages\/index.astro<\/code> file, and at the top, we\u2019ll import the rss-parser and instantiate the <code>Partner<\/code> class.<\/p>\n<pre><code class=\"language-javascript\">import Parser from 'rss-parser';\nconst parser = new Parser();\n<\/code><\/pre>\n<p>We use this parser to read our RSS feeds and (surprise!) <em>parse<\/em> them to JavaScript. We\u2019re going to be dealing with a list of promises here. Normally, I would probably use <code>Promise.all()<\/code>, but the thing is, this is supposed to be a complicated experience. If one of the feeds doesn\u2019t work for some reason, I\u2019d prefer to simply ignore it.<\/p>\n<p>Why? Well, because <code>Promise.all()<\/code> rejects everything even if only one of its promises is rejected. That might mean that if one feed doesn\u2019t behave the way I\u2019d expect it to, my entire page would be blank when I grab my hot beverage to read the news in the morning. I do not want to start my day confronted by an error.<\/p>\n<p>Instead, I\u2019ll opt to use <code>Promise.allSettled()<\/code>. This method will actually let all promises complete even if one of them fails. In our case, this means any feed that errors will just be ignored, which is perfect.<\/p>\n<p>Let\u2019s add this to the <code>src\/pages\/index.astro<\/code> file:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-typescript\">interface FeedItem {\n feed?: string;\n title?: string;\n link?: string;\n date?: Date;\n}\n\nconst feedItems: FeedItem[] = [];\n\nawait Promise.allSettled(\n feedSources.map(async (source) => {\n try {\n const feed = await parser.parseURL(source);\n feed.items.forEach((item) => {\n const date = item.pubDate ? new Date(item.pubDate) : undefined;\n \n feedItems.push({\n feed: feed.title,\n title: item.title,\n link: item.link,\n date,\n });\n });\n } catch (error) {\n console.error(`Error fetching feed from ${source}:`, error);\n }\n })\n);\n<\/code><\/pre>\n<\/div>\n<p>This creates an array (or more) named <code>feedItems<\/code>. For each URL in the <code>feedSources<\/code> array we created earlier, the rss-parser retrieves the items and, yes, parses them into JavaScript. Then, we return whatever data we want! We\u2019ll keep it simple for now and only return the following:<\/p>\n<ul>\n<li>The feed title,<\/li>\n<li>The title of the feed item,<\/li>\n<li>The link to the item,<\/li>\n<li>And the item\u2019s published date.<\/li>\n<\/ul>\n<p>The next step is to ensure that all items are sorted by date so we\u2019ll truly get the \u201clatest\u201d news. Add this small piece of code to our work:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-typescript\">const sortedFeedItems = feedItems.sort((a, b) => (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime());\n<\/code><\/pre>\n<\/div>\n<p>Oh, and… remember when I said I didn\u2019t want this RSS reader to render anything older than seven days? Let\u2019s tackle that right now since we\u2019re already in this code.<\/p>\n<p>We\u2019ll make a new variable called <code>sevenDaysAgo<\/code> and assign it a date. We\u2019ll then set that date to seven days ago and use that logic before we add a new item to our <code>feedItems<\/code> array.<\/p>\n<p>This is what the <code>src\/pages\/index.astro<\/code> file should now look like at this point:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-typescript\">---\nimport Layout from '..\/layouts\/Layout.astro';\nimport Parser from 'rss-parser';\nconst parser = new Parser();\n\nconst sevenDaysAgo = new Date();\nsevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);\n\nconst feedSources = [\n 'https:\/\/www.smashingmagazine.com\/feed\/',\n 'https:\/\/developer.mozilla.org\/en-US\/blog\/rss.xml',\n]\n\ninterface FeedItem {\n feed?: string;\n title?: string;\n link?: string;\n date?: Date;\n}\n\nconst feedItems: FeedItem[] = [];\n\nawait Promise.allSettled(\n feedSources.map(async (source) => {\n try {\n const feed = await parser.parseURL(source);\n feed.items.forEach((item) => {\n const date = item.pubDate ? new Date(item.pubDate) : undefined;\n if (date && date >= sevenDaysAgo) {\n feedItems.push({\n feed: feed.title,\n title: item.title,\n link: item.link,\n date,\n });\n }\n });\n } catch (error) {\n console.error(`Error fetching feed from ${source}:`, error);\n }\n })\n);\n\nconst sortedFeedItems = feedItems.sort((a, b) => (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime());\n\n---\n\n<Layout title=\"Welcome to Astro.\">\n <main>\n <\/main>\n<\/Layout>\n<\/code><\/pre>\n<\/div>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"rendering-xml-data\">Rendering XML Data<\/h2>\n<p>It\u2019s time to show our news articles on the Astro site! To keep this simple, we\u2019ll format the items in an unordered list rather than some other fancy layout.<\/p>\n<p>All we need to do is update the <code><Layout><\/code> element in the file with the XML objects sprinkled in for a feed item\u2019s title, URL, and publish date.<\/p>\n<pre><code class=\"language-html\"><Layout title=\"Welcome to Astro.\">\n <main>\n {sortedFeedItems.map(item => (\n <ul>\n <li>\n <a href={item.link}>{item.title}<\/a>\n <p>{item.feed}<\/p>\n <p>{item.date}<\/p>\n <\/li>\n <\/ul>\n ))}\n <\/main>\n<\/Layout>\n<\/code><\/pre>\n<p>Go ahead and run <code>pnpm start<\/code> from the terminal. The page should display an unordered list of feed items. Of course, everything is styled at the moment, but luckily for you, you can make it look exactly like you want with CSS!<\/p>\n<p>And remember that there are even <strong>more fields available in the XML for each item<\/strong> if you want to display more information. If you run the following snippet in your DevTools console, you\u2019ll see all of the fields you have at your disposal:<\/p>\n<pre><code class=\"language-javascript\">feed.items.forEach(item => {}\n<\/code><\/pre>\n<h2 id=\"scheduling-daily-static-site-builds\">Scheduling Daily Static Site Builds<\/h2>\n<p>We\u2019re nearly done! The feeds are being fetched, and they are returning data back to us in JavaScript for use in our Astro page template. Since feeds are updated whenever new content is published, we need a way to fetch the latest items from it.<\/p>\n<p>We want to avoid doing any of this manually. So, let\u2019s set this site on Netlify to gain access to their scheduled functions that trigger a rebuild and their build hooks that do the building. Again, other services do this, and you\u2019re welcome to roll this work with another provider — I\u2019m just partial to Netlify since I work there. In any case, you can follow Netlify\u2019s documentation for <a href=\"https:\/\/docs.netlify.com\/welcome\/add-new-site\/#import-from-an-existing-repository\">setting up a new site<\/a>.<\/p>\n<p>Once your site is hosted and live, you are ready to schedule your rebuilds. A <a href=\"https:\/\/docs.netlify.com\/configure-builds\/build-hooks\/\">build hook<\/a> gives you a URL to use to trigger the new build, looking something like this:<\/p>\n<pre><code class=\"language-html\">https:\/\/api.netlify.com\/build_hooks\/your-build-hook-id\n<\/code><\/pre>\n<p>Let\u2019s trigger builds every day at midnight. We\u2019ll use Netlify\u2019s <a href=\"https:\/\/docs.netlify.com\/functions\/scheduled-functions\/\">scheduled functions<\/a>. That\u2019s really why I\u2019m using Netlify to host this in the first place. Having them at the ready via the host greatly simplifies things since there\u2019s no server work or complicated configurations to get this going. Set it and forget it!<\/p>\n<p>We\u2019ll install <code>@netlify\/functions<\/code> (<a href=\"https:\/\/docs.netlify.com\/functions\/get-started\/\">instructions<\/a>) to the project and then create the following file in the project\u2019s root directory: <code>netlify\/functions\/deploy.ts<\/code>.<\/p>\n<p>This is what we want to add to that file:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-typescript\">\/\/ netlify\/functions\/deploy.ts\n\nimport type { Config } from '@netlify\/functions';\n\nconst BUILD_HOOK =\n 'https:\/\/api.netlify.com\/build_hooks\/your-build-hook-id'; \/\/ replace me!\n\nexport default async (req: Request) => {\n await fetch(BUILD_HOOK, {\n method: 'POST',\n })\n};\n\nexport const config: Config = {\n schedule: '0 0 * * *',\n};\n<\/code><\/pre>\n<\/div>\n<p>If you commit your code and push it, your site should re-deploy automatically. From that point on, it follows a schedule that rebuilds the site every day at midnight, ready for you to take your morning brew and catch up on everything that <em>you<\/em> think is important.<\/p>\n<div class=\"signature\">\n <img decoding=\"async\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" class=\"lazyload\" data-src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\"><br \/>\n <span>(gg, yk)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Build A Static RSS Reader To Fight Your Inner FOMO Build A Static RSS Reader To Fight Your Inner FOMO Karin Hendrikse 2024-10-07T13:00:00+00:00 2024-10-15T23:05:45+00:00 In a fast-paced industry like tech, it can be hard to deal with the fear of missing out on important news. But, as many of us know, there\u2019s an absolutely huge…<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-430","post","type-post","status-publish","format-standard","hentry","category-javascript"],"_links":{"self":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/430","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/comments?post=430"}],"version-history":[{"count":1,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/430\/revisions"}],"predecessor-version":[{"id":431,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/430\/revisions\/431"}],"wp:attachment":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/media?parent=430"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/categories?post=430"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/tags?post=430"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}