From c8536502bcdf8668bb12edc23d807036391a54fb Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 15 Sep 2025 08:27:57 +0200 Subject: [PATCH] Add RSS feed functionality and icon to the blog --- app/feed.xml/route.ts | 35 ++++++++++++++++++++++++++++++++++ app/layout.tsx | 8 ++++++++ bun.lock | 12 ++++++++++++ components/rss-icon.tsx | 42 +++++++++++++++++++++++++++++++++++++++++ components/window.tsx | 9 +++++++-- package.json | 4 +++- 6 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 app/feed.xml/route.ts create mode 100644 components/rss-icon.tsx diff --git a/app/feed.xml/route.ts b/app/feed.xml/route.ts new file mode 100644 index 0000000..3a29f97 --- /dev/null +++ b/app/feed.xml/route.ts @@ -0,0 +1,35 @@ + +import RSS from "rss"; +import { getSortedPostsData } from "@/lib/posts"; + +export async function GET() { + const feed = new RSS({ + title: "Gabriel Kaszewski's Blog", + description: "A personal blog by Gabriel Kaszewski about technology, programming, and game development.", + site_url: "https://blog.gabrielkaszewski.dev", + feed_url: "https://blog.gabrielkaszewski.dev/feed.xml", + language: "en", + pubDate: new Date(), + copyright: `© ${new Date().getFullYear()} Gabriel Kaszewski`, + }); + + const posts = getSortedPostsData(); + + posts.forEach((post) => { + feed.item({ + title: post.title, + description: post.description, + url: `https://blog.gabrielkaszewski.dev/posts/${post.id}`, + guid: post.id, + date: post.date, + }); + }); + + const xml = feed.xml({ indent: true }); + + return new Response(xml, { + headers: { + "Content-Type": "application/xml; charset=utf-8", + }, + }); +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 6e82fbc..6a7b5e6 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -45,6 +45,14 @@ export const metadata: Metadata = { }, ], }, + other: { + "msapplication-TileColor": "#da532c", + "theme-color": "#ffffff", + "Content-Type": "application/rss+xml", + rel: "alternate", + title: "Gabriel Kaszewski's Blog RSS Feed", + url: "/feed.xml", + }, }; export default function RootLayout({ diff --git a/bun.lock b/bun.lock index cc74c0d..73fead5 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": "blog", "dependencies": { "@tailwindcss/typography": "^0.5.16", + "@types/rss": "^0.0.32", "cursor-effects": "^1.0.17", "gray-matter": "^4.0.3", "next": "15.5.2", @@ -14,6 +15,7 @@ "reading-time": "^1.5.0", "remark": "^15.0.1", "remark-html": "^16.0.1", + "rss": "^1.2.2", }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -166,6 +168,8 @@ "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], + "@types/rss": ["@types/rss@0.0.32", "", {}, "sha512-2oKNqKyUY4RSdvl5eZR1n2Q9yvw3XTe3mQHsFPn9alaNBxfPnbXBtGP8R0SV8pK1PrVnLul0zx7izbm5/gF5Qw=="], + "@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -398,6 +402,10 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "mime-db": ["mime-db@1.25.0", "", {}, "sha512-5k547tI4Cy+Lddr/hdjNbBEWBwSl8EBc5aSdKvedav8DReADgWJzcYiktaRIw3GtGC1jjwldXtTzvqJZmtvC7w=="], + + "mime-types": ["mime-types@2.1.13", "", { "dependencies": { "mime-db": "~1.25.0" } }, "sha512-ryBDp1Z/6X90UvjUK3RksH0IBPM137T7cmg4OgD5wQBojlAiUwuok0QeELkim/72EtcYuNlmbkrcGuxj3Kl0YQ=="], + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], @@ -450,6 +458,8 @@ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + "rss": ["rss@1.2.2", "", { "dependencies": { "mime-types": "2.1.13", "xml": "1.0.1" } }, "sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg=="], + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], @@ -518,6 +528,8 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "xml": ["xml@1.0.1", "", {}, "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], diff --git a/components/rss-icon.tsx b/components/rss-icon.tsx new file mode 100644 index 0000000..4309235 --- /dev/null +++ b/components/rss-icon.tsx @@ -0,0 +1,42 @@ +const RSSIcon = (props: React.SVGProps) => ( + + + + + + + + + + +); + +export default RSSIcon; diff --git a/components/window.tsx b/components/window.tsx index 8da6d6b..a137868 100644 --- a/components/window.tsx +++ b/components/window.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import React from "react"; +import RSSIcon from "./rss-icon"; const CloseIcon = () => ( - {/* Title Bar with gradient and controls */}
- {title} +
+ {title} + + + +
diff --git a/package.json b/package.json index fac3884..e90a9c6 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@tailwindcss/typography": "^0.5.16", + "@types/rss": "^0.0.32", "cursor-effects": "^1.0.17", "gray-matter": "^4.0.3", "next": "15.5.2", @@ -17,7 +18,8 @@ "react-dom": "19.1.0", "reading-time": "^1.5.0", "remark": "^15.0.1", - "remark-html": "^16.0.1" + "remark-html": "^16.0.1", + "rss": "^1.2.2" }, "devDependencies": { "typescript": "^5",