Opens an external site in a new window
Mental Health Awareness Month
“Community”
RODNEY LAB
  • Home
  • Plus +
  • Newsletter
  • Links
  • Profile
RODNEY LAB
  • Home
  • Plus +
  • Newsletter
  • Links

Deno Fresh RSS Feed: own your Content 📜 # Deno Fresh RSS Feed: own your Content 📜 #

blurry low resolution placeholder image Deno Fresh RSS Feed
  1. Home Rodney Lab Home
  2. Blog Posts Rodney Lab Blog Posts
  3. Deno Deno Blog Posts
<PREVIOUS POST
NEXT POST >
LATEST POST >>

Deno Fresh RSS Feed: own your Content 📜 #

Updated 6 months ago
5 minute read
Gunning Fog Index: 4.9
Content by Rodney
blurry low resolution placeholder image Author Image: Rodney from Rodney Lab
SHARE:

🦖 Deno Fresh RSS Feed: Acing HTTP Security Headers #

Now is a great time to set up your Deno Fresh RSS Feed. That is because there is uncertainty over the direction of travel of a popular social network. This may leave your followers turning to alternative feeds to get their daily fix of updates. With RSS you publish a feed on your site, your followers subscribe. Now they have an auto-updating list of your latest content, all in chronological order. What’s more, Deno Fresh makes it easy for you to create that self-updating feed. In the video, you see how to serve an RSS feed from a resource route on your Deno site. On top, we look at caching headers and adding the right content to the head section on your site HTML pages to advertise the feed.

Anyway, that is enough of an introduction, if you still think you’re in the right place then hit play below on the video! You can drop a comment below or reach out for a chat on Element  as well as Twitter @mention  if you have suggestions for improvements or questions.

📹 Video #

Please enable JavaScript to watch the video 📼

Deno Fresh RSS Feed: own your Content

🗳 Poll #

Do you already use an RSS reader?
Voting reveals latest results.

🖥 Deno Fresh RSS Feed: Code #

Blog Utility Functions #

utility/blog.ts — click to expand code.
utility/blog.ts
typescript
    
1 import { extractYaml } from "@std/front-matter";
2
3 export interface PostMeta {
4 slug: string;
5 postTitle: string;
6 datePublished: string;
7 seoMetaDescription: string;
8 featuredImage: string;
9 }
10
11 export interface Post extends PostMeta {
12 content: string;
13 }
14
15 export async function loadPost(slug: string): Promise<Post | null> {
16 let text: string;
17 try {
18 text = await Deno.readTextFile(`./data/posts/${slug}/index.md`);
19 } catch (error: unknown) {
20 if (error instanceof Deno.errors.NotFound) {
21 return null;
22 }
23 console.error(`Error loading: ${error}`);
24 throw error;
25 }
26 const { attrs, body } = extractYaml(text);
27 const { datePublished, featuredImage, seoMetaDescription, postTitle } =
28 attrs as Record<string, string>;
29
30 return {
31 content: body,
32 featuredImage,
33 postTitle,
34 datePublished,
35 seoMetaDescription,
36 slug,
37 };
38 }
39
40 export async function loadPostMeta(slug: string): Promise<PostMeta | null> {
41 let text: string;
42 try {
43 text = await Deno.readTextFile(`./data/posts/${slug}/index.md`);
44 } catch (error: unknown) {
45 if (error instanceof Deno.errors.NotFound) {
46 return null;
47 }
48 console.error(`Error loading issue ${slug}: ${error}`);
49 throw error;
50 }
51 const {
52 attrs: { postTitle, datePublished, seoMetaDescription, featuredImage },
53 } = extractYaml<PostMeta>(text);
54
55 return { slug, postTitle, datePublished, seoMetaDescription, featuredImage };
56 }
57
58 function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
59 return value !== null && value !== undefined;
60 }
61
62 export async function posts(): Promise<PostMeta[]> {
63 const promises = [];
64 for await (const entry of Deno.readDir("./data/posts")) {
65 const slug = entry.name;
66 promises.push(loadPostMeta(slug));
67 }
68 const posts = (await Promise.all(promises)).filter(notEmpty);
69 return posts.sort(
70 (a, b) => Date.parse(b.datePublished) - Date.parse(a.datePublished),
71 );
72 }

Net Utility Function #

utility/net.ts — click to expand code.
utility/net.ts
typescript
    
1 export function getDomainUrl(request: Request) {
2 const host = request.headers.get("X-Forwarded-Host") ??
3 request.headers.get("host");
4 if (!host) {
5 throw new Error("Could not determine domain URL.");
6 }
7 const protocol = host.includes("localhost") ? "http" : "https";
8 return `${protocol}://${host}`;
9 }

RSS Feed (resource route) #

routes/xml.rss — click to expand code.
routes/xml.rss
typescript
    
1 import { Handlers } from "$fresh/server.ts";
2 import { getDomainUrl } from "@/utility/net.ts";
3 import website from "@/configuration/website.ts";
4 import { posts } from "@/utility/blog.ts";
5
6 function escapeCdata(value: string) {
7 return value.replace(/]]>/g, "]]]]><![CDATA[>");
8 }
9
10 function escapeHtml(html: string) {
11 return html
12 .replace(/&/g, "&amp;")
13 .replace(/</g, "&lt;")
14 .replace(/>/g, "&gt;")
15 .replace(/"/g, "&quot;")
16 .replace(/'/g, "&#039;");
17 }
18
19 export const handler: Handlers = {
20 async GET(request) {
21 const domainUrl = getDomainUrl(request);
22
23 const { author, rssSiteLanguage, siteTitle } = website;
24
25 const allPosts = await posts();
26
27 const rssString = `
28 <rss xmlns:blogChannel="${domainUrl}" version="2.0">
29 <channel>
30 <title>${siteTitle}</title>
31 <link>${domainUrl}</link>
32 <description>${siteTitle}</description>
33 <language>${rssSiteLanguage}</language>
34 <ttl>40</ttl>
35 ${allPosts
36 .map(({ slug, datePublished, seoMetaDescription, postTitle }) =>
37 `
38 <item>
39 <title><![CDATA[${escapeCdata(postTitle)}]]></title>
40 <description><![CDATA[${escapeHtml(
41 seoMetaDescription
42 )}]]></description>
43 <author><![CDATA[${escapeCdata(author)}]]></author>
44 <pubDate>${datePublished}</pubDate>
45 <link>${domainUrl}/${slug}</link>
46 <guid>${domainUrl}/${slug}</guid>
47 </item>
48 `.trim()
49 )
50 .join("
51 ")}
52 </channel>
53 </rss>`.trim();
54
55 const headers = new Headers({
56 "Cache-Control": `public, max-age=${60 * 10}, s-maxage=${60 * 60 * 24}`,
57 "Content-Type": "application/xml",
58 });
59
60 return new Response(rssString, { headers });
61 },
62 };

HTML Head Snippet (advertising feed) #

    
<link
rel="alternate"
title="Rodney Lab Camera Blog"
href="/rss.xml"
type="application/rss+xml" />

🔗 Links #

  • getting started with Deno Fresh post
  • complete code for this project on GitHub 
  • Deno docs on front matter extract 
  • Raven RSS Reader 
  • Feeder RSS Reader 
  • Element chat: #rodney matrix chat 
  • Twitter handle: @askRodney 

🏁 Deno Fresh RSS Feed: Summary #

Does Deno Fresh have an RSS Feed plugin? #

At the time of writing there is no plugin, but we have seen it is pretty easy to set up something flexible yourself, using a resource route in Deno Fresh. Deno gives you back control using the platform to let you add features like an RSS feed exactly the way you want it! The resource route will have a handler function which listens for GET requests. This is not too different to handlers you might already be using on your HTML routes. You can build up the XML body of the response within the handler, then return it (as a new Response object). We saw how you just have to remember to add a couple of headers for content type and caching.

What are resource routes, and how do you add them in Deno Fresh? #

While pages on your Deno Fresh site serve HTML content, you can use resource routes to serve a wider range of content formats. We saw an XML RSS Feed example, but you could also serve a PDF brochure, an e-book, or even some data in CSV or JSON format. To create the resource route, you follow the same file-base routing system used for your HTML pages. Just the file extension will be `.ts`. Let’s say you want to serve your autobiography as an e-book from `https://example.com/books/my-life.epub`. Following the file-based routing system, you would place the handler in `routes/books/my-life.epub.ts`. That’s it! The handler returns an HTTP response, and the body (in this case) would be the binary bytes of your book file. Remember also to use an appropriate content-type header!

How do you parse Markdown front matter in Deno? #

Deno has an extract function defined in `std/encoding/front_matter/yaml.ts`. You can import this at the top of your source file and use it to parse valid YAML front matter into a TypeScript object. First read the file into a variable using `const text = await Deno.readTextFile('..path/to/markdown.md);`. Next, you can call the extract function on this text, destructuring `attrs` like this: `const {attrs} = extract(text);`. Note that `extract` supports generics, so if you have a type alias for your front matter, you can use it thus: `extract<MyFrontMatterType>(text)`. Finally, let’s say you have a title field in your front matter. You can access it using `attrs.title`.

🙏🏽 Deno Fresh RSS Feed: Feedback #

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.

blurry low resolution placeholder image ask Rodney X (formerly Twitter) avatar

Rodney

@askRodney

With 📜 RSS making a comeback I put together a short video on adding an RSS Feed to your Deno 🍋 Fresh site.

Should be handy if you want to create an XML sitemap for SEO purposes too!

Hope you find it useful!

#learndeno #askrodney #usetheplatformhttps://t.co/c1ny7Mf6gj

— Rodney (@askRodney) January 27, 2023

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter, @[email protected]  on Mastodon and also the #rodney  Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as Deno. Also, subscribe to the newsletter to keep up-to-date with our latest projects.

Thanks for reading this post. I hope you found it valuable. Please get in touch with your feedback and suggestions for posts you would like to see. Read more about me …

blurry low resolution placeholder image Rodney from Rodney Lab
TAGS:
DENO

Likes:

Likes

  • Tanja Gomilar profile avatar
  • Webjeda 🛹 profile avatar
  • 愛花(28)@自由なCEO profile avatar
Likes provided by Mastodon & X via Webmentions.
<PREVIOUS POST
NEXT POST >
LATEST POST >>

Leave a comment …

Your information will be handled in line with our Privacy Policy .

Ask for more

1 Nov 2022 — Astro Server-Side Rendering: Edge Search Site
3 Oct 2022 — Svelte eCommerce Site: SvelteKit Snipcart Storefront
1 Sept 2022 — Get Started with SvelteKit Headless WordPress

Copyright © 2020 – 2025 Rodney Johnson. All Rights Reserved. Please read important copyright and intellectual property information.

  • Home
  • Profile
  • Plus +
  • Newsletter
  • Contact
  • Links
  • Terms of Use
  • Privacy Policy
We use cookies  to enhance visitors’ experience. Please click the “Options” button to make your choice.  Learn more here.