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"