diff --git a/app/[lang]/blog/[slug]/page.tsx b/app/[lang]/blog/[slug]/page.tsx
index 1f251c7..8ffac8a 100644
--- a/app/[lang]/blog/[slug]/page.tsx
+++ b/app/[lang]/blog/[slug]/page.tsx
@@ -61,6 +61,11 @@ export default function BlogArticle({ params }: any) {
const imageAsCover = true
+ const isNewsletter =
+ post?.title?.toLowerCase().includes("newsletter") ||
+ post?.tags?.includes("newsletter") ||
+ post?.tldr?.toLowerCase()?.includes("newsletter")
+
return (
@@ -150,7 +155,11 @@ export default function BlogArticle({ params }: any) {
-
+
)
diff --git a/app/api/rss/route.ts b/app/api/rss/route.ts
new file mode 100644
index 0000000..8fd3a70
--- /dev/null
+++ b/app/api/rss/route.ts
@@ -0,0 +1,36 @@
+import { NextResponse } from "next/server"
+import { generateRssFeed } from "@/lib/rss"
+
+export const dynamic = "force-dynamic"
+export const revalidate = 3600 // Revalidate every hour
+
+export async function GET(request: Request) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const lang = searchParams.get("lang") || "en"
+
+ console.log("Generating RSS feed for language:", lang)
+ const feed = await generateRssFeed(lang)
+ console.log("RSS feed generated successfully")
+
+ return new NextResponse(feed, {
+ headers: {
+ "Content-Type": "application/xml",
+ "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=1800",
+ },
+ })
+ } catch (error) {
+ console.error("Error generating RSS feed:", error)
+ if (error instanceof Error) {
+ console.error("Error details:", {
+ message: error.message,
+ stack: error.stack,
+ name: error.name,
+ })
+ }
+ return new NextResponse(
+ `Error generating RSS feed: ${error instanceof Error ? error.message : "Unknown error"}`,
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index fe3544c..2fad430 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -59,6 +59,16 @@ export const metadata: Metadata = {
},
],
},
+ alternates: {
+ types: {
+ "application/rss+xml": [
+ {
+ url: "/api/rss",
+ title: "RSS Feed for Privacy & Scaling Explorations",
+ },
+ ],
+ },
+ },
}
interface RootLayoutProps {
diff --git a/components/blog/blog-content.tsx b/components/blog/blog-content.tsx
index 935257a..fede226 100644
--- a/components/blog/blog-content.tsx
+++ b/components/blog/blog-content.tsx
@@ -10,6 +10,7 @@ import { ArticleListCard } from "./article-list-card"
interface BlogContentProps {
post: Article
lang: LocaleTypes
+ isNewsletter?: boolean
}
interface BlogImageProps {
@@ -36,7 +37,11 @@ export function BlogImage({ image, alt, description }: BlogImageProps) {
)
}
-export function BlogContent({ post, lang }: BlogContentProps) {
+export function BlogContent({
+ post,
+ lang,
+ isNewsletter = false,
+}: BlogContentProps) {
const articles = getArticles() ?? []
const articleIndex = articles.findIndex((article) => article.id === post.id)
@@ -54,10 +59,12 @@ export function BlogContent({ post, lang }: BlogContentProps) {
{post?.content ?? ""}
-
+ {!isNewsletter && (
+
+ )}
{moreArticles?.length > 0 && (
diff --git a/components/icons.tsx b/components/icons.tsx
index 3658657..15e763c 100644
--- a/components/icons.tsx
+++ b/components/icons.tsx
@@ -235,6 +235,18 @@ export const Icons = {
/>
),
+ rss: (props: LucideProps) => (
+
+ ),
readme: (props: LucideProps) => (
-
+
+
+
+
+
+ }
+ />
+
file.endsWith(".md") && file !== "_article-template.md")
+ .map((file) => {
+ try {
+ const filePath = path.join(articlesDirectory, file)
+ const fileContents = fs.readFileSync(filePath, "utf8")
+ const { data, content } = matter(fileContents)
+
+ return {
+ slug: file.replace(/\.md$/, ""),
+ frontmatter: data,
+ content,
+ }
+ } catch (error) {
+ console.warn(`Error processing article ${file}:`, error)
+ return null
+ }
+ })
+ .filter(
+ (article): article is NonNullable => article !== null
+ )
+ .sort(
+ (a, b) =>
+ formatDate(b.frontmatter.date).getTime() -
+ formatDate(a.frontmatter.date).getTime()
+ )
+
+ // Add articles to feed
+ articles.forEach((article) => {
+ try {
+ const url = `${SITE_URL}/articles/${article.slug}`
+ const pubDate = formatDate(article.frontmatter.date)
+
+ feed.addItem({
+ title: article.frontmatter.title || "Untitled Article",
+ id: url,
+ link: url,
+ description: article.frontmatter.tldr || "",
+ content: article.content,
+ author: article.frontmatter.authors?.map((author: string) => ({
+ name: author,
+ })) || [{ name: "PSE Team" }],
+ date: pubDate,
+ image: article.frontmatter.image
+ ? `${SITE_URL}${article.frontmatter.image}`
+ : undefined,
+ category:
+ article.frontmatter.tags?.map((tag: string) => ({ name: tag })) ||
+ [],
+ })
+ } catch (error) {
+ console.warn(`Error adding article ${article.slug} to feed:`, error)
+ }
+ })
+
+ return feed.rss2()
+ } catch (error) {
+ console.error("Error generating RSS feed:", error)
+ throw error
+ }
+}
diff --git a/package.json b/package.json
index 334386f..75fdbaf 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"clsx": "^1.2.1",
"discord.js": "14.4.0",
"dotenv": "^16.4.4",
+ "feed": "^5.1.0",
"framer-motion": "^10.12.17",
"fuse.js": "^6.6.2",
"gray-matter": "^4.0.3",
diff --git a/yarn.lock b/yarn.lock
index 93d5e9e..42b9815 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4173,6 +4173,15 @@ __metadata:
languageName: node
linkType: hard
+"feed@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "feed@npm:5.1.0"
+ dependencies:
+ xml-js: "npm:^1.6.11"
+ checksum: 10/c0b7cd6e6a5d5406f871cdaf4487d273c00e35f3c04975bb9095e10cc5234ec1b797d11a37f6502476d212e1ba0854f3de7302c4750bdeb7aec3aedcd4e50426
+ languageName: node
+ linkType: hard
+
"file-entry-cache@npm:^8.0.0":
version: 8.0.0
resolution: "file-entry-cache@npm:8.0.0"
@@ -6857,6 +6866,7 @@ __metadata:
eslint-config-prettier: "npm:^8.8.0"
eslint-plugin-react: "npm:^7.37.4"
eslint-plugin-tailwindcss: "npm:^3.18.0"
+ feed: "npm:^5.1.0"
framer-motion: "npm:^10.12.17"
fuse.js: "npm:^6.6.2"
globals: "npm:^15.14.0"
@@ -8079,6 +8089,13 @@ __metadata:
languageName: node
linkType: hard
+"sax@npm:^1.2.4":
+ version: 1.4.1
+ resolution: "sax@npm:1.4.1"
+ checksum: 10/b1c784b545019187b53a0c28edb4f6314951c971e2963a69739c6ce222bfbc767e54d320e689352daba79b7d5e06d22b5d7113b99336219d6e93718e2f99d335
+ languageName: node
+ linkType: hard
+
"scheduler@npm:^0.23.2":
version: 0.23.2
resolution: "scheduler@npm:0.23.2"
@@ -9713,6 +9730,17 @@ __metadata:
languageName: node
linkType: hard
+"xml-js@npm:^1.6.11":
+ version: 1.6.11
+ resolution: "xml-js@npm:1.6.11"
+ dependencies:
+ sax: "npm:^1.2.4"
+ bin:
+ xml-js: ./bin/cli.js
+ checksum: 10/55ce342a47bf14a138a3fcea0c9e325b81484cfc1a8aac78df13b4d6ca01f20e32820572bc3e927cd9b61b9da9cdee4657cb2f304e460343d8d85d6a3659d749
+ languageName: node
+ linkType: hard
+
"yallist@npm:^3.0.2":
version: 3.1.1
resolution: "yallist@npm:3.1.1"