An RSS Reader That Refreshes Itself
RSS remains one of the simplest ways to subscribe to content across the web. An XML-based standard, it lets you pull the latest articles, news, and updates from any site that publishes a feed. Rather than scrolling through endless timelines, you can use RSS to build a tailored digest that updates on your schedule — not the platform’s.
In this guide, we’ll use Astro, TypeScript, rss-parser, and Netlify’s scheduled functions and build hooks to create a static site that fetches curated feeds only once a day. The result is a lightweight RSS reader you control completely.
Why a Static Site?
A static approach means the RSS content is fetched at build time, not on every page visit. That’s a deliberate choice: it keeps the site fast, avoids unnecessary requests, and makes it easy to schedule updates. Netlify’s scheduled functions can trigger a rebuild at a specific time — say, midnight — and the new content is deployed automatically. No manual checking or server maintenance is needed.
For the parsing step, we’ll use rss-parser, a small library that converts feed XML into JavaScript objects. It also supports filters, so we can limit results to the last day or week. We’ll only show items from the past seven days to keep the reading list manageable.
Setting Up the Astro Project
Start by creating a new Astro site in your terminal:
pnpm create astro@latest
Astro’s CLI will ask a few setup questions. Choose the sample files option if you want a quick starting point, then strip out the default content inside the <main></main> tags in src/pages/index.astro. You can run pnpm start to see the basic site locally.
Defining Your Feed Sources
In src/pages/index.astro, between Astro’s frontmatter fences, define an array of feed URLs. For example:
const feedSources = [
'https://www.smashingmagazine.com/feed/',
'https://developer.mozilla.org/en-US/blog/rss.xml',
// etc.
]
Next, install the parser:
pnpm install rss-parser
Then import and instantiate it in the same file:
import Parser from 'rss-parser';
const parser = new Parser();
Now fetch and parse each feed. Rather than using Promise.all(), which fails entirely if one feed errors, we use Promise.allSettled(). This way one broken feed doesn’t blank out the whole page.
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(`Error fetching feed from ${source}:`, error);
}
})
);
This returns an array of items with the essential fields: feed title, item title, link, and publish date.
Sorting and Filtering
To display the newest items first, sort the combined array by date:
const sortedFeedItems = feedItems.sort((a, b) => (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime());
Then, limit results to the last seven days. Compute a threshold date and only include items newer than that:
---
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 = [
'https://www.smashingmagazine.com/feed/',
'https://developer.mozilla.org/en-US/blog/rss.xml',
]
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(`Error fetching feed from ${source}:`, 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>
The full frontmatter at this point should assemble the feed items, apply the seven-day cutoff, and prepare the data for rendering.
Rendering the Feed Items
Now we display the parsed data in the page template. Insert the feed items as a simple unordered list inside the <Layout> section, pulling the relevant fields for each entry:
<Layout title="Welcome to Astro.">
<main>
{sortedFeedItems.map(item => (
<ul>
<li>
<a href={item.link}>{item.title}</a>
<p>{item.feed}</p>
<p>{item.date}</p>
</li>
</ul>
))}
</main>
</Layout>
Run pnpm start again — you’ll see a plain list of the latest articles from your chosen sources. Styling is entirely up to you with CSS. If you want richer detail later, the RSS items contain more fields than we’re using. To inspect all available data, open your browser’s DevTools console and run:
feed.items.forEach(item => {}
Automating Daily Builds
We want fresh content automatically, without manual rebuilds. Host the site on Netlify and make use of two features: build hooks and scheduled functions. A build hook provides a unique URL that, when requested, triggers a new deployment:
https://api.netlify.com/build_hooks/your-build-hook-id
Install @netlify/functions into the project, then create netlify/functions/deploy.ts:
// netlify/functions/deploy.ts
import type { Config } from '@netlify/functions';
const BUILD_HOOK =
'https://api.netlify.com/build_hooks/your-build-hook-id'; // replace me!
export default async (req: Request) => {
await fetch(BUILD_HOOK, {
method: 'POST',
})
};
export const config: Config = {
schedule: '0 0 * * *',
};
This scheduled function will call your build hook URL every day at midnight. Push the code, and the schedule is live — the site rebuilds and redeploys daily with the latest RSS content, ready for your next read.




