Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
"lucide-react": "^0.511.0",
"next": "15.1.8",
"next": "15.2.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
Expand All @@ -47,7 +47,7 @@
"@types/react-dom": "^19",
"chokidar-cli": "^3.0.0",
"eslint": "^9",
"eslint-config-next": "15.1.8",
"eslint-config-next": "15.2.4",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5",
Expand Down
11 changes: 10 additions & 1 deletion src/app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,19 @@ export default async function BlogPage() {
</main>

{/* Footer */}
<footer className="border-t border-gray-200 py-8 mt-16">
<footer
id="blog-footer"
className="border-t border-gray-200 py-8 mt-16"
>
<div className="max-w-4xl mx-auto px-6 flex flex-col md:flex-row justify-between items-center gap-4 text-sm font-sans text-gray-500">
<p>© {new Date().getFullYear()} Ivan Leo. All rights reserved.</p>
<div className="flex gap-6">
<a
href="/rss.xml"
className="hover:text-black transition-colors"
>
RSS
</a>
<a
href="https://twitter.com/ivanleomk"
target="_blank"
Expand Down
8 changes: 8 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<head>
<link
rel="alternate"
type="application/rss+xml"
title="Ivan Leo - Blog RSS"
href="/rss.xml"
/>
</head>
<body
className={`${lato.variable} ${crimsonText.variable} antialiased`}
>
Expand Down
59 changes: 59 additions & 0 deletions src/app/rss.xml/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { getAllPosts } from "@/lib/blog";

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://ivanleo.com";

function escapeXml(value: string): string {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}

export const revalidate = 3600;

export async function GET(): Promise<Response> {
const posts = await getAllPosts();
const publishedPosts = posts.filter((post) => !post.draft);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

The filter for published posts doesn't validate that posts have required fields (title, description, date, slug). If a post is missing these fields, the RSS feed will have malformed items. Consider adding validation or filtering out posts without required fields.

Agent: 🤖 General • Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: ivanleomk/website-v4#23
File: src/app/rss.xml/route.ts#L18
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
The filter for published posts doesn't validate that posts have required fields (title, description, date, slug). If a post is missing these fields, the RSS feed will have malformed items. Consider adding validation or filtering out posts without required fields.


const items = publishedPosts
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.map((post) => {
const link = `${baseUrl}/blog/${post.slug}`;
const categories = post.categories
.map((category) => `<category>${escapeXml(category)}</category>`)
.join("");

return `
<item>
<title>${escapeXml(post.title)}</title>
<link>${link}</link>
<guid>${link}</guid>
<description>${escapeXml(post.description)}</description>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
${categories}
</item>
`;
})
.join("");

const feed = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Ivan Leo - Blog</title>
<link>${baseUrl}/blog</link>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low

The baseUrl in the channel link is not being escaped. While unlikely to contain special characters, for consistency and robustness: <link>${escapeXml(baseUrl)}/blog</link>

Agent: 🤖 General • Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: ivanleomk/website-v4#23
File: src/app/rss.xml/route.ts#L45
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
The baseUrl in the channel link is not being escaped. While unlikely to contain special characters, for consistency and robustness: `<link>${escapeXml(baseUrl)}/blog</link>`

<description>Ivan rambles on about LLM reliability, evals and UX design</description>
<language>en-us</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
${items}
</channel>
</rss>
`;

return new Response(feed.trim(), {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
},
});
}