diff --git a/blog/app/layout.tsx b/blog/app/layout.tsx index 9ea4fe5..4438c92 100644 --- a/blog/app/layout.tsx +++ b/blog/app/layout.tsx @@ -1,12 +1,18 @@ import './globals.css'; +import { Metadata } from 'next'; import { ReactNode } from 'react'; import { Navbar } from '@/components/navbar/Navbar'; -export const metadata = { +export const metadata: Metadata = { title: 'ENS Blog', description: 'The official blog of the Ethereum Name Service', + alternates: { + types: { + 'application/atom+xml': '/rss.xml', + }, + }, }; export default function RootLayout({ children }: { children: ReactNode }) { diff --git a/blog/app/rss.xml/route.ts b/blog/app/rss.xml/route.ts new file mode 100644 index 0000000..eb880bf --- /dev/null +++ b/blog/app/rss.xml/route.ts @@ -0,0 +1,50 @@ +import { covers } from 'assets/assets'; +import { Feed } from 'feed'; + +import { getPostsMetadata } from '@/lib/get_posts'; + +export const GET = async (request: Request) => { + const feed = new Feed({ + title: 'ENS Blog', + description: 'The official blog of the Ethereum Name Service', + link: 'https://blog.ens.domains', + language: 'en', + image: 'https://blog.ens.domains/opengraph.jpg', + generator: 'NextJS on Edgeserver', + feedLinks: { + atom: 'https://blog.ens.domains/rss.xml', + }, + id: 'https://blog.ens.domains', + copyright: '© ENS Domains', + }); + + const posts = await getPostsMetadata(); + + for (const post of posts) { + const postCovers = covers[post.file as keyof typeof covers]; + const postCoverThumb = await postCovers['cover-thumb'].then( + (cover) => cover.default + ); + + feed.addItem({ + title: post.title, + guid: 'https://blog.ens.domains/post/' + post.slug, + link: 'https://blog.ens.domains/post/' + post.slug, + date: new Date(post.date), + description: post.description, + author: post.authors.map((author) => { + return { + name: author, + link: 'https://blog.ens.domains/author/' + author, + }; + }), + image: postCoverThumb.src, + }); + } + + return new Response(feed.atom1(), { + headers: { + 'Content-Type': 'application/atom+xml; charset=utf-8', + }, + }); +}; diff --git a/blog/package.json b/blog/package.json index 83c6694..0d6c693 100644 --- a/blog/package.json +++ b/blog/package.json @@ -30,6 +30,7 @@ "deepmerge-ts": "^5.1.0", "ens-tools": "0.0.15-1", "eslint-plugin-tailwindcss": "^3.13.0", + "feed": "^4.2.2", "framer-motion": "^10.16.0", "mdast-util-to-markdown": "^2.1.0", "mdast-util-to-string": "^4.0.0", diff --git a/blog/pnpm-lock.yaml b/blog/pnpm-lock.yaml index 0b2a686..3f03718 100644 --- a/blog/pnpm-lock.yaml +++ b/blog/pnpm-lock.yaml @@ -50,6 +50,9 @@ dependencies: eslint-plugin-tailwindcss: specifier: ^3.13.0 version: 3.13.0(tailwindcss@3.3.3) + feed: + specifier: ^4.2.2 + version: 4.2.2 framer-motion: specifier: ^10.16.0 version: 10.16.0(react-dom@18.2.0)(react@18.2.0) @@ -4225,6 +4228,13 @@ packages: dependencies: reusify: 1.0.4 + /feed@4.2.2: + resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} + engines: {node: '>=0.4.0'} + dependencies: + xml-js: 1.6.11 + dev: false + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -7016,6 +7026,10 @@ packages: engines: {node: '>=10'} dev: false + /sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + dev: false + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -8314,6 +8328,13 @@ packages: utf-8-validate: 5.0.10 dev: false + /xml-js@1.6.11: + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} + hasBin: true + dependencies: + sax: 1.3.0 + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}