diff --git a/apps/foundation/app/(Site)/blog/layout.tsx b/apps/foundation/app/(Site)/blog/layout.tsx index add9f9e1..70978667 100644 --- a/apps/foundation/app/(Site)/blog/layout.tsx +++ b/apps/foundation/app/(Site)/blog/layout.tsx @@ -4,10 +4,13 @@ import { getInitialSiteDataForSSR } from '@/lib/sanity/sanity-server'; import type { Metadata, ResolvingMetadata } from 'next'; import { generateSanityMetadata } from '@session/sanity-cms/lib/metadata'; import { client } from '@/lib/sanity/sanity.client'; +import { generateRssFeed } from '@/lib/rss'; export async function generateMetadata(_: object, parent: ResolvingMetadata): Promise { const { settings } = await getInitialSiteDataForSSR(); + await generateRssFeed(); + return settings.blogSeo ? await generateSanityMetadata(client, { seo: settings.blogSeo, diff --git a/apps/foundation/app/api/revalidate/route.ts b/apps/foundation/app/api/revalidate/route.ts index 7042d0ff..85aa739e 100644 --- a/apps/foundation/app/api/revalidate/route.ts +++ b/apps/foundation/app/api/revalidate/route.ts @@ -1,5 +1,6 @@ import { createRevalidateHandler } from '@session/sanity-cms/api/revalidate'; import { SANITY_SCHEMA_URL } from '@/lib/constants'; +import { generateRssFeed } from '@/lib/rss'; const SANITY_REVALIDATE_SECRET = process.env.SANITY_REVALIDATE_SECRET!; if (!SANITY_REVALIDATE_SECRET) { @@ -12,4 +13,10 @@ export const { POST } = createRevalidateHandler({ page: SANITY_SCHEMA_URL.PAGE, post: SANITY_SCHEMA_URL.POST, }, + rssGenerators: [ + { + _type: 'post', + generateRssFeed, + }, + ], }); diff --git a/apps/foundation/app/sitemap.ts b/apps/foundation/app/sitemap.ts index 5a4faafd..2a1a1aba 100644 --- a/apps/foundation/app/sitemap.ts +++ b/apps/foundation/app/sitemap.ts @@ -18,7 +18,7 @@ export default async function sitemap(): Promise { priority: 1, }, ...pages.map((page) => ({ - url: `${BASE_URL}${page.slug.current}`, + url: `${BASE_URL}/${page.slug.current}`, lastModified: page._updatedAt, changeFrequency: 'weekly' as const, priority: 0.5, diff --git a/apps/foundation/lib/constants.ts b/apps/foundation/lib/constants.ts index 495168b4..fe4abebc 100644 --- a/apps/foundation/lib/constants.ts +++ b/apps/foundation/lib/constants.ts @@ -1,6 +1,6 @@ import { Social, SocialLink } from '@session/ui/components/SocialLinkList'; -export const BASE_URL = `https://session.foundation/`; +export const BASE_URL = `https://session.foundation`; export const SOCIALS = { [Social.Github]: { name: Social.Github, link: 'https://github.com/oxen-io/websites' }, diff --git a/apps/foundation/lib/rss.ts b/apps/foundation/lib/rss.ts new file mode 100644 index 00000000..8cf83e00 --- /dev/null +++ b/apps/foundation/lib/rss.ts @@ -0,0 +1,73 @@ +import fs from 'fs'; +import { generateXMLFromObject } from '@session/sanity-cms/lib/xml'; +import { getSiteSettings } from '@session/sanity-cms/queries/getSiteSettings'; +import { client } from '@/lib/sanity/sanity.client'; +import { BASE_URL, SANITY_SCHEMA_URL } from '@/lib/constants'; +import { getPostsWithMetadata } from '@session/sanity-cms/queries/getPosts'; + +export async function generateRssFeed() { + const posts = await getPostsWithMetadata({ client }); + + posts.sort((a, b) => { + const dateA = new Date(a.date ?? a._updatedAt); + const dateB = new Date(b.date ?? b._updatedAt); + + return dateB.getTime() - dateA.getTime(); + }); + + const date = new Date(); + + const settings = await getSiteSettings({ client }); + + const copyright = settings?.copyright; + + const title = + settings?.blogSeo?.metaTitle ?? + settings?.blogSeo?.openGraph?.title ?? + settings?.seo?.metaTitle ?? + settings?.seo?.openGraph?.title; + + const json = { + rss: { + '@version': '2.0', + '@xmlns:atom': 'http://www.w3.org/2005/Atom', + '@xmlns:content': 'http://purl.org/rss/1.0/modules/content/', + '@xmlns:dc': 'http://purl.org/dc/elements/1.1/', + channel: { + title, + description: 'RSS feed for Session Technology Foundation updates.', + link: BASE_URL, + image: { + url: `${BASE_URL}/images/logoBlack.png`, + title: `Session Technology Foundation updates`, + link: BASE_URL, + }, + generator: 'mini-xml for Node.js', + lastBuildDate: date, + 'atom:link': { + '@href': `${BASE_URL}/rss.xml`, + '@rel': 'self', + '@type': 'application/rss+xml', + }, + copyright, + item: posts.map((post) => { + const url = `${BASE_URL}${SANITY_SCHEMA_URL.POST}${post.slug.current}`; + return { + title: post.title.trim(), + description: post.summary.trim(), + link: url, + guid: { + '@isPermaLink': 'true', + '#text': url, + }, + pubDate: new Date(post.date ?? post._updatedAt), + }; + }), + }, + }, + }; + + const feed = generateXMLFromObject(json, { pretty: true, indentSpaces: 2 }); + + fs.writeFileSync('./public/rss.xml', feed); +} diff --git a/apps/foundation/next.config.mjs b/apps/foundation/next.config.mjs index 01076b45..12c0b614 100644 --- a/apps/foundation/next.config.mjs +++ b/apps/foundation/next.config.mjs @@ -22,6 +22,23 @@ const nextConfig = { images: { remotePatterns: [{ hostname: 'cdn.sanity.io' }], }, + async rewrites() { + return [ + ...[ + '/rss', + '/feed', + '/feed.xml', + '/blog.xml', + '/blog/rss', + '/blog/feed', + '/blog/rss.xml', + '/blog/feed.xml', + ].map((source) => ({ + source, + destination: '/rss.xml', + })), + ]; + }, }; export default withNextIntl(withPlaiceholder(nextConfig));