Build A Static RSS Reader To Fight Your Inner FOMO

RSS is a classic technology that fetches content from websites and feeds it to anyone who subscribes to it with a URL. It’s based on XML, and we can use it to consume the feeds in our own apps. Karin Hendrikse demonstrates how to do exactly that with a static site you can use as your personal RSS reader.

Jan 19, 2025 - 17:33
 0  3
Build A Static RSS Reader To Fight Your Inner FOMO

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’s 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 an RSS feed is a delightful way of taking back ownership of our own time. In this article, we will create a static Really Simple Syndication (RSS) reader that will bring you the latest curated news only once (yes: once) a day.

We’ll obviously work with RSS technology in the process, but we’re also going to combine it with some things that maybe you haven’t tried before, including Astro (the static site framework), TypeScript (for JavaScript goodies), a package called rss-parser (for connecting things together), as well as scheduled functions and build hooks provided by Netlify (although there are other services that do this).

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’d like. The most important thing is getting the end result! The Plan

Here’s 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’s something we can control each time the site is “rebuilt” and redeployed with updates. That’s where Netlify’s 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.

During the triggered rebuild, we’ll 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’ll get there!

But first... What Is RSS?

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’s 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 Smashing Magazine’s RSS feed: https://www.smashingmagazine.com/feed/). 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.

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’ll give you a link to the RSS specification. Don’t 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’re only using the following elements: </code></strong>, <strong><code><link></code></strong>, <strong><code><description></code></strong>, <strong><code><item></code></strong>, and <strong><code><pubDate></code></strong>. We’ll also let our RSS parser package do some of the work for us. Creating The State Site <p>We’ll 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’m simply trying out <a href="https://pnpm.io/">pnpm</a> for myself. <p>After running the command, Astro’s chat-based helper, Houston, walks through some setup questions to get things started. <pre><code> astro Launch sequence initiated. dir Where should we create your new project? ./rss-buddy tmpl How would you like to start your new project? Include sample files ts Do you plan to write TypeScript? Yes use How strict should TypeScript be? Strict deps Install dependencies? Yes git Initialize a new git repository? Yes </code></pre> <p>I like to use Astro’s sample files so I can get started quickly, but we’re going to clean them up a bit in the process. Let’s clean up the <code>src/pages/index.astro</code> file by removing everything inside of the <code><main></main></code> tags. Then we’re good to go! <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. Pulling Information From RSS feeds <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’s 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: <pre><code>const feedSources = [ 'https://www.smashingmagazine.com/feed/', 'https://developer.mozilla.org/en-US/blog/rss.xml', // etc. ] </code></pre> <p>Now we’ll 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>Once the package is installed, open the <code>src/pages/index.astro</code> file, and at the top, we’ll import the rss-parser and instantiate the <code>Partner</code> class. <pre><code>import Parser from 'rss-parser'; const parser = new Parser(); </code></pre> <p>We use this parser to read our RSS feeds and (surprise!) <em>parse</em> them to JavaScript. We’re 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’t work for some reason, I’d prefer to simply ignore it. <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’t behave the way I’d 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>Instead, I’ll 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>Let’s add this to the <code>src/pages/index.astro</code> file: <div> <pre><code>interface FeedItem { feed?: string; title?: string; link?: string; date?: Date; } const feedItems: FeedItem[] = []; await Promise.allSettled( feedSources.map(async (source) => { try { const feed = await parser.parseURL(source); feed.items.forEach((item) => { const date = item.pubDate ? new Date(item.pubDate) : undefined; feedItems.push({ feed: feed.title, title: item.title, link: item.link, date, }); }); } catch (error) { console.error(<code>Error fetching feed from ${source}:</code>, error); } }) ); </code></pre> </div> <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’ll keep it simple for now and only return the following: <ul> <li>The feed title,</li> <li>The title of the feed item,</li> <li>The link to the item,</li> <li>And the item’s published date.</li> </ul> <p>The next step is to ensure that all items are sorted by date so we’ll truly get the “latest” news. Add this small piece of code to our work: <div> <pre><code>const sortedFeedItems = feedItems.sort((a, b) => (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime()); </code></pre> </div> <p>Oh, and... remember when I said I didn’t want this RSS reader to render anything older than seven days? Let’s tackle that right now since we’re already in this code. <p>We’ll make a new variable called <code>sevenDaysAgo</code> and assign it a date. We’ll 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>This is what the <code>src/pages/index.astro</code> file should now look like at this point: <div> <pre><code>--- import Layout from '../layouts/Layout.astro'; import Parser from 'rss-parser'; const parser = new Parser(); const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); const feedSources = [ '<a href="https://www.smashingmagazine.com/feed/'">https://www.smashingmagazine.com/feed/'</a>, '<a href="https://developer.mozilla.org/en-US/blog/rss.xml'">https://developer.mozilla.org/en-US/blog/rss.xml'</a>, ] interface FeedItem { feed?: string; title?: string; link?: string; date?: Date; } const feedItems: FeedItem[] = []; await Promise.allSettled( feedSources.map(async (source) => { try { const feed = await parser.parseURL(source); feed.items.forEach((item) => { const date = item.pubDate ? new Date(item.pubDate) : undefined; if (date && date >= sevenDaysAgo) { feedItems.push({ feed: feed.title, title: item.title, link: item.link, date, }); } }); } catch (error) { console.error(<code>Error fetching feed from ${source}:</code>, error); } }) ); const sortedFeedItems = feedItems.sort((a, b) => (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime()); --- <Layout title="Welcome to Astro."> <main> </main> </Layout> </code></pre> </div> Rendering XML Data <p>It’s time to show our news articles on the Astro site! To keep this simple, we’ll format the items in an unordered list rather than some other fancy layout. <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’s title, URL, and publish date. <pre><code><Layout title="Welcome to Astro."> <main> {sortedFeedItems.map(item => ( <ul> <li> <a href={item.link}>{item.title}</a> <p>{item.feed} <p>{item.date} </li> </ul> ))} </main> </Layout> </code></pre> <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>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’ll see all of the fields you have at your disposal: <pre><code>feed.items.forEach(item => {} </code></pre> Scheduling Daily Static Site Builds <p>We’re 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>We want to avoid doing any of this manually. So, let’s 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’re welcome to roll this work with another provider — I’m just partial to Netlify since I work there. In any case, you can follow Netlify’s documentation for <a href="https://docs.netlify.com/welcome/add-new-site/#import-from-an-existing-repository">setting up a new site</a>. <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: <pre><code>https://api.netlify.com/build_hooks/your-build-hook-id </code></pre> <p>Let’s trigger builds every day at midnight. We’ll use Netlify’s <a href="https://docs.netlify.com/functions/scheduled-functions/">scheduled functions</a>. That’s really why I’m using Netlify to host this in the first place. Having them at the ready via the host greatly simplifies things since there’s no server work or complicated configurations to get this going. Set it and forget it! <p>We’ll 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’s root directory: <code>netlify/functions/deploy.ts</code>. <p>This is what we want to add to that file: <div> <pre><code>// netlify/functions/deploy.ts import type { Config } from '@netlify/functions'; const BUILD_HOOK = '<a href="https://api.netlify.com/build_hooks/your-build-hook-id'">https://api.netlify.com/build_hooks/your-build-hook-id'</a>; // replace me! export default async (req: Request) => { await fetch(BUILD_HOOK, { method: 'POST', }) }; export const config: Config = { schedule: '0 0 * * *', }; </code></pre> </div> <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. </div> <div class="d-flex flex-row-reverse mt-4"> <a href="https://smashingmagazine.com/2024/10/build-static-rss-reader-fight-fomo/" class="btn btn-md btn-custom" target="_blank" rel="nofollow"> Read More <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="m-l-5" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/> </svg> </a> </div> <div class="d-flex flex-row post-tags align-items-center mt-5"> <h2 class="title">Tags:</h2> <ul class="d-flex flex-row"> </ul> </div> <div class="post-next-prev mt-5"> <div class="row"> <div class="col-sm-6 col-xs-12 left"> <div class="head-title text-end"> <a href="https://theshapeofdreams.com/using-multimodal-ai-models-for-your-applications-part-3"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/> </svg> Previous Article </a> </div> <h3 class="title text-end"> <a href="https://theshapeofdreams.com/using-multimodal-ai-models-for-your-applications-part-3">Using Multimodal AI Models For Your Applications (Part 3)</a> </h3> </div> <div class="col-sm-6 col-xs-12 right"> <div class="head-title text-start"> <a href="https://theshapeofdreams.com/how-a-bottom-up-design-approach-enhances-site-accessibility"> Next Article <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/> </svg> </a> </div> <h3 class="title text-start"> <a href="https://theshapeofdreams.com/how-a-bottom-up-design-approach-enhances-site-accessibility">How A Bottom-Up Design Approach Enhances Site Accessibility</a> </h3> </div> </div> </div> <div class="row"> <div class="col-sm-12 col-xs-12 reactions noselect"> <h4 class="title-reactions">What's Your Reaction?</h4> <div id="reactions_result"> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'like');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/like.png" alt="like" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Like</label> </p> </div> </div> </div> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'dislike');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/dislike.png" alt="dislike" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Dislike</label> </p> </div> </div> </div> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'love');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/love.png" alt="love" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Love</label> </p> </div> </div> </div> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'funny');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/funny.png" alt="funny" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Funny</label> </p> </div> </div> </div> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'angry');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/angry.png" alt="angry" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Angry</label> </p> </div> </div> </div> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'sad');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/sad.png" alt="sad" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Sad</label> </p> </div> </div> </div> <div class="col-reaction col-reaction-like" onclick="addReaction('254', 'wow');"> <div class="col-sm-12"> <div class="row"> <div class="icon-cnt"> <img src="https://theshapeofdreams.com/assets/img/reactions/wow.png" alt="wow" class="img-reaction"> <label class="label reaction-num-votes">0</label> </div> </div> <div class="row"> <p class="text-center"> <label class="label label-reaction ">Wow</label> </p> </div> </div> </div> </div> </div> </div> <div class="d-flex about-author"> <div class="flex-shrink-0"> <a href="https://theshapeofdreams.com/profile/admin" class="author-link"> <img src="https://theshapeofdreams.com/assets/img/user.png" alt="admin" class="img-fluid img-author" width="110" height="110"> </a> </div> <div class="flex-grow-1 ms-3"> <strong class="username"><a href="https://theshapeofdreams.com/profile/admin"> admin </a></strong> </div> </div> <section class="section section-related-posts mt-5"> <div class="row"> <div class="col-12"> <div class="section-title"> <div class="d-flex justify-content-between align-items-center"> <h3 class="title">Related Posts</h3> </div> </div> <div class="section-content"> <div class="row"> <div class="col-sm-12 col-md-6 col-lg-4"> <div class="post-item"> <div class="image ratio"> <a href="https://theshapeofdreams.com/navigating-the-challenges-of-modern-open-source-authoring-lessons-learned"> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAEYAQMAAAD1c2RPAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAACVJREFUaN7twQEBAAAAgqD+r26IwAAAAAAAAAAAAAAAAAAAACDoP3AAASZRMyIAAAAASUVORK5CYII=" data-src="https://theshapeofdreams.com/uploads/images/202501/image_430x256_6798d9a021b7c.jpg" alt="Navigating The Challenges Of Modern Open-Source Authoring: Lessons Learned" class="img-fluid lazyload" width="269" height="160"/> </a> </div> <h3 class="title fsize-16"><a href="https://theshapeofdreams.com/navigating-the-challenges-of-modern-open-source-authoring-lessons-learned">Navigating The Challenges Of Modern Open-Source Authori...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 28, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 12</span> </p> </div> </div> <div class="col-sm-12 col-md-6 col-lg-4"> <div class="post-item"> <div class="image ratio"> <a href="https://theshapeofdreams.com/get-your-apps-ready-for-16-kb-page-size-devices"> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAEYAQMAAAD1c2RPAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAACVJREFUaN7twQEBAAAAgqD+r26IwAAAAAAAAAAAAAAAAAAAACDoP3AAASZRMyIAAAAASUVORK5CYII=" data-src="https://theshapeofdreams.com/uploads/images/202501/image_430x256_678d17697feac.jpg" alt="Get your apps ready for 16 KB page size devices" class="img-fluid lazyload" width="269" height="160"/> </a> </div> <h3 class="title fsize-16"><a href="https://theshapeofdreams.com/get-your-apps-ready-for-16-kb-page-size-devices">Get your apps ready for 16 KB page size devices</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 19, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 1</span> </p> </div> </div> <div class="col-sm-12 col-md-6 col-lg-4"> <div class="post-item post-item-no-image"> <h3 class="title fsize-16"><a href="https://theshapeofdreams.com/find-user-name-find-user">Find User Name | Find User</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 19, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 3</span> </p> </div> </div> </div> </div> </div> </div> </section> <section class="section section-comments mt-5"> <div class="row"> <div class="col-12"> <div class="nav nav-tabs" id="navTabsComment" role="tablist"> <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#navComments" type="button" role="tab">Comments</button> </div> <div class="tab-content" id="navTabsComment"> <div class="tab-pane fade show active" id="navComments" role="tabpanel" aria-labelledby="nav-home-tab"> <form id="add_comment"> <input type="hidden" name="parent_id" value="0"> <input type="hidden" name="post_id" value="254"> <div class="form-row"> <div class="row"> <div class="form-group col-md-6"> <label>Name</label> <input type="text" name="name" class="form-control form-input" maxlength="40" placeholder="Name"> </div> <div class="form-group col-md-6"> <label>Email</label> <input type="email" name="email" class="form-control form-input" maxlength="100" placeholder="Email"> </div> </div> </div> <div class="form-group"> <label>Comment</label> <textarea name="comment" class="form-control form-input form-textarea" maxlength="4999" placeholder="Leave your comment..."></textarea> </div> <div class="form-group"> <script src="https://www.google.com/recaptcha/api.js?hl=en"></script><div class="g-recaptcha" data-sitekey="6LfoU7wqAAAAAFV2V7Yt39ir4TUj-NTnJHFYsZmf"></div> </div> <button type="submit" class="btn btn-md btn-custom">Post Comment</button> </form> <div id="message-comment-result" class="message-comment-result"></div> <div id="comment-result"> <input type="hidden" value="5" id="post_comment_limit"> <div class="row"> <div class="col-sm-12"> <div class="comments"> <ul class="comment-list"> </ul> </div> </div> </div> </div> </div> </div> </div> </div> </section> </div> </div> <div class="col-md-12 col-lg-4"> <div class="col-sidebar sticky-lg-top"> <div class="row"> <div class="col-12"> <div class="sidebar-widget"> <div class="widget-head"><h4 class="title">Popular Posts</h4></div> <div class="widget-body"> <div class="row"> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/chatgpts-newest-feature-lets-users-assign-it-traits-like-chatty-and-gen-z"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_678cdd26791d8.jpg" alt="ChatGPT’s newest feature lets users assign it traits like ‘chatty’ and ‘Gen Z’" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/chatgpts-newest-feature-lets-users-assign-it-traits-like-chatty-and-gen-z">ChatGPT’s newest feature lets users assign it trai...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 19, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 62</span> </p> </div> </div> </div> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/trump-tells-putin-to-end-ridiculous-war-in-ukraine-or-face-new-sanctions"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_6791de08f269d.jpg" alt="Trump tells Putin to end 'ridiculous war' in Ukraine or face new sanctions" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/trump-tells-putin-to-end-ridiculous-war-in-ukraine-or-face-new-sanctions">Trump tells Putin to end 'ridiculous war' in Ukrai...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 23, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 54</span> </p> </div> </div> </div> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/a-mockery-trumps-new-meme-coin-sparks-anger-in-crypto-world"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_6799bf51bd3b3.jpg" alt="'A mockery': Trump's new meme-coin sparks anger in crypto world" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/a-mockery-trumps-new-meme-coin-sparks-anger-in-crypto-world">'A mockery': Trump's new meme-coin sparks anger in...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 29, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 54</span> </p> </div> </div> </div> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/cloud-cost-management-optimizing-your-cloud-spending-with-accrets"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_678f304ddaa5e.jpg" alt="Cloud Cost Management: Optimizing Your Cloud Spending with Accrets" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/cloud-cost-management-optimizing-your-cloud-spending-with-accrets">Cloud Cost Management: Optimizing Your Cloud Spend...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 21, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 45</span> </p> </div> </div> </div> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/the-ball-gowns-of-trumps-new-golden-age"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_6798d8d84c1e6.jpg" alt="The Ball Gowns of Trump’s New ‘Golden Age’" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/the-ball-gowns-of-trumps-new-golden-age">The Ball Gowns of Trump’s New ‘Golden Age’</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 28, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 39</span> </p> </div> </div> </div> </div> </div> </div> <div class="sidebar-widget"> <div class="widget-head"><h4 class="title">Recommended Posts</h4></div> <div class="widget-body"> <div class="row"> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/grammy-awards-2025-a-night-of-music-glamour-and-surprises"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202502/image_140x98_679fab44a89bb.jpg" alt="Grammy Awards 2025 A Night of Music, Glamour, and Surprises" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/grammy-awards-2025-a-night-of-music-glamour-and-surprises">Grammy Awards 2025 A Night of Music, Glamour, and...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Feb 2, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 14</span> </p> </div> </div> </div> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/india-vs-england-india-sets-a-challenging-target-in-the-fifth-t20-with-abishek-sharmas-brilliance"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202502/image_140x98_679f937e882c6.jpg" alt="India vs England: India Sets a Challenging Target in the Fifth T20 with Abishek Sharma's Brilliance" class="img-fluid lazyload" width="130" height="91"/> <span class="media-icon media-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#ececec"viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445z"/></svg></span> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/india-vs-england-india-sets-a-challenging-target-in-the-fifth-t20-with-abishek-sharmas-brilliance">India vs England: India Sets a Challenging Target ...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Feb 2, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 17</span> </p> </div> </div> </div> <div class="col-12"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/fitness-influencer-chris-odonnell-tiktoks-creeohdee-dies-at-31"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://www.etonline.com/sites/default/files/styles/dist_rss/public/images/2025-01/ETD_OBIT_CREEOHDEE_011725_TRT0148_GR_VIDPIC_16x9.jpg?h=d1cb525d#" alt="Fitness Influencer Chris O’Donnell, TikTok's Creeohdee, Dies at 31" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/fitness-influencer-chris-odonnell-tiktoks-creeohdee-dies-at-31">Fitness Influencer Chris O’Donnell, TikTok's Creeo...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 19, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 8</span> </p> </div> </div> </div> </div> </div> </div> <div class="sidebar-widget"> <div class="widget-head"><h4 class="title">Popular Tags</h4></div> <div class="widget-body"> <ul class="tag-list"> </ul> </div> </div> </div> </div> </div> </div> </div> </div> </section> <style> .post-text img { display: none !important; } .post-content .post-summary { display: none; } </style> <script type="application/ld+json">[{ "@context": "http://schema.org", "@type": "Organization", "url": "https://theshapeofdreams.com", "logo": {"@type": "ImageObject","width": 190,"height": 60,"url": "https://theshapeofdreams.com/uploads/logo/logo_678cd8781f2047-65874743.png"}}, { "@context": "http://schema.org", "@type": "WebSite", "url": "https://theshapeofdreams.com", "potentialAction": { "@type": "SearchAction", "target": "https://theshapeofdreams.com/search?q={search_term_string}", "query-input": "required name=search_term_string" } }] </script> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "NewsArticle", "mainEntityOfPage": { "@type": "WebPage", "@id": "https://theshapeofdreams.com/build-a-static-rss-reader-to-fight-your-inner-fomo" }, "headline": "Build A Static RSS Reader To Fight Your Inner FOMO", "name": "Build A Static RSS Reader To Fight Your Inner FOMO", "articleSection": "Web Development Tutorials", "image": { "@type": "ImageObject", "url": "https://theshapeofdreams.com/uploads/images/202501/image_870x580_678cea08e5bc7.jpg", "width": 750, "height": 500 }, "datePublished": "2025-01-19T17:33:20+0530", "dateModified": "2025-01-19T17:33:20+0530", "inLanguage": "en-US", "keywords": "Build, Static, RSS, Reader, Fight, Your, Inner, FOMO", "author": { "@type": "Person", "name": "admin" }, "publisher": { "@type": "Organization", "name": "the shape of dreams", "logo": { "@type": "ImageObject", "width": 190, "height": 60, "url": "https://theshapeofdreams.com/uploads/logo/logo_678cd8781f2047-65874743.png" } }, "description": "RSS is a classic technology that fetches content from websites and feeds it to anyone who subscribes to it with a URL. It’s based on XML, and we can use it to consume the feeds in our own apps. Karin Hendrikse demonstrates how to do exactly that with a static site you can use as your personal RSS reader." } </script> <footer id="footer"> <div class="footer-inner"> <div class="container-xl"> <div class="row justify-content-between"> <div class="col-sm-12 col-md-6 col-lg-4 footer-widget footer-widget-about"> <div class="footer-logo"> <img src="https://theshapeofdreams.com/uploads/logo/logo_678cd8781f2751-70011044.png" alt="logo" class="logo" width="240" height="90"> </div> <div class="footer-about"> Discover a universe of creativity at The Shape of Dreams. From the latest news and music trends to coding tutorials, videos, and scripts—delve into a world of inspiration and innovation. Stay informed and inspired every day. </div> </div> <div class="col-sm-12 col-md-6 col-lg-4 footer-widget"> <h4 class="widget-title">Most Viewed Posts</h4> <div class="footer-posts"> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/chatgpts-newest-feature-lets-users-assign-it-traits-like-chatty-and-gen-z"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_678cdd26791d8.jpg" alt="ChatGPT’s newest feature lets users assign it traits like ‘chatty’ and ‘Gen Z’" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/chatgpts-newest-feature-lets-users-assign-it-traits-like-chatty-and-gen-z">ChatGPT’s newest feature lets users assign it trai...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 19, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 62</span> </p> </div> </div> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/trump-tells-putin-to-end-ridiculous-war-in-ukraine-or-face-new-sanctions"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_6791de08f269d.jpg" alt="Trump tells Putin to end 'ridiculous war' in Ukraine or face new sanctions" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/trump-tells-putin-to-end-ridiculous-war-in-ukraine-or-face-new-sanctions">Trump tells Putin to end 'ridiculous war' in Ukrai...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 23, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 54</span> </p> </div> </div> <div class="tbl-container post-item-small"> <div class="tbl-cell left"> <div class="image"> <a href="https://theshapeofdreams.com/a-mockery-trumps-new-meme-coin-sparks-anger-in-crypto-world"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="https://theshapeofdreams.com/uploads/images/202501/image_140x98_6799bf51bd3b3.jpg" alt="'A mockery': Trump's new meme-coin sparks anger in crypto world" class="img-fluid lazyload" width="130" height="91"/> </a> </div> </div> <div class="tbl-cell right"> <h3 class="title"><a href="https://theshapeofdreams.com/a-mockery-trumps-new-meme-coin-sparks-anger-in-crypto-world">'A mockery': Trump's new meme-coin sparks anger in...</a></h3> <p class="small-post-meta"> <a href="https://theshapeofdreams.com/profile/admin" class="a-username">admin</a> <span>Jan 29, 2025</span> <span><i class="icon-comment"></i> 0</span> <span class="m-r-0"><i class="icon-eye"></i> 54</span> </p> </div> </div> </div> </div> <div class="col-sm-12 col-md-6 col-lg-4 footer-widget"> <h4 class="widget-title">Newsletter</h4> <div class="newsletter"> <p class="description">Join our subscribers list to get the latest news, updates and special offers directly in your inbox</p> <form id="form_newsletter_footer" class="form-newsletter"> <div class="newsletter-inputs"> <input type="email" name="email" class="form-control form-input newsletter-input" maxlength="199" placeholder="Email"> <button type="submit" name="submit" value="form" class="btn btn-custom newsletter-button">Subscribe</button> </div> <input type="text" name="url"> <div id="form_newsletter_response"></div> </form> </div> <div class="footer-social-links"> <ul> </ul> </div> </div> </div> </div> </div> <div class="footer-copyright"> <div class="container-xl"> <div class="row align-items-center"> <div class="col-sm-12 col-md-6"> <div class="copyright text-start"> Copyright 2025 theshapeofdreams.com - All Rights Reserved. </div> </div> <div class="col-sm-12 col-md-6"> <div class="nav-footer text-end"> <ul> <li><a href="https://theshapeofdreams.com/dmca">Dmca </a></li> <li><a href="https://theshapeofdreams.com/terms-conditions">Terms & Conditions </a></li> </ul> </div> </div> </div> </div> </div> </footer> <a href="#" class="scrollup"><i class="icon-arrow-up"></i></a> <script src="https://theshapeofdreams.com/assets/themes/magazine/js/jquery-3.6.1.min.js "></script> <script src="https://theshapeofdreams.com/assets/vendor/bootstrap/js/bootstrap.bundle.min.js "></script> <script src="https://theshapeofdreams.com/assets/themes/magazine/js/plugins.js "></script> <script src="https://theshapeofdreams.com/assets/themes/magazine/js/main-2.2.min.js "></script> <script>$("form[method='post']").append("<input type='hidden' name='sys_lang_id' value='1'>");</script> </body> </html>