From 3326d1a29016e0898e806932d0bfec7997929d03 Mon Sep 17 00:00:00 2001 From: John Pham Date: Tue, 4 Aug 2026 17:57:52 -0500 Subject: [PATCH] Stabilize docs RSS dates Signed-off-by: John Pham --- apps/docs/app/[lang]/rss.xml/route.ts | 12 +++++------ apps/docs/lib/geistdocs/rss.test.ts | 29 +++++++++++++++++++++++++++ apps/docs/lib/geistdocs/rss.ts | 29 +++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 apps/docs/lib/geistdocs/rss.test.ts create mode 100644 apps/docs/lib/geistdocs/rss.ts diff --git a/apps/docs/app/[lang]/rss.xml/route.ts b/apps/docs/app/[lang]/rss.xml/route.ts index 73243aec5..c8f6eb3f4 100644 --- a/apps/docs/app/[lang]/rss.xml/route.ts +++ b/apps/docs/app/[lang]/rss.xml/route.ts @@ -1,6 +1,7 @@ import { Feed } from "feed"; import type { NextRequest } from "next/server"; import { title } from "@/geistdocs"; +import { getFeedUpdatedAt, selectDatedFeedPages } from "@/lib/geistdocs/rss"; import { source } from "@/lib/geistdocs/source"; import { getSiteOrigin } from "@/lib/geistdocs/url"; @@ -8,28 +9,25 @@ const baseUrl = getSiteOrigin(); export const revalidate = false; -const getLastModified = (data: object) => - "lastModified" in data && data.lastModified instanceof Date ? data.lastModified : undefined; - export const GET = async (_req: NextRequest, { params }: RouteContext<"/[lang]/rss.xml">) => { const { lang } = await params; + const pages = selectDatedFeedPages(source.getPages(lang)); const feed = new Feed({ title, id: baseUrl, link: baseUrl, language: lang, + updated: getFeedUpdatedAt(pages), copyright: `All rights reserved ${new Date().getFullYear()}, Vercel`, }); - for (const page of source.getPages(lang)) { - const lastModified = getLastModified(page.data); - + for (const { lastModified, page } of pages) { feed.addItem({ id: page.url, title: page.data.title ?? page.url, description: page.data.description, link: `${baseUrl}${page.url}`, - date: lastModified ?? new Date(), + date: lastModified, author: [ { name: "Vercel", diff --git a/apps/docs/lib/geistdocs/rss.test.ts b/apps/docs/lib/geistdocs/rss.test.ts new file mode 100644 index 000000000..10e96f8a0 --- /dev/null +++ b/apps/docs/lib/geistdocs/rss.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { getFeedUpdatedAt, selectDatedFeedPages } from "./rss"; + +describe("RSS dates", () => { + it("keeps every page and uses a stable fallback for untrustworthy source dates", () => { + const dated = new Date("2026-07-01T00:00:00.000Z"); + const pages = [ + { url: "/dated", data: { lastModified: dated } }, + { url: "/undated", data: {} }, + { url: "/invalid", data: { lastModified: "2026-08-04" } }, + ]; + + expect(selectDatedFeedPages(pages)).toEqual([ + { page: pages[0], lastModified: dated }, + { page: pages[1], lastModified: new Date("2026-06-17T00:00:00.000Z") }, + { page: pages[2], lastModified: new Date("2026-06-17T00:00:00.000Z") }, + ]); + }); + + it("uses the latest source date without request-time fallback", () => { + const pages = selectDatedFeedPages([ + { data: { lastModified: new Date("2026-07-01T00:00:00.000Z") } }, + { data: { lastModified: new Date("2026-07-03T00:00:00.000Z") } }, + ]); + + expect(getFeedUpdatedAt(pages).toISOString()).toBe("2026-07-03T00:00:00.000Z"); + expect(getFeedUpdatedAt([]).toISOString()).toBe("2026-06-17T00:00:00.000Z"); + }); +}); diff --git a/apps/docs/lib/geistdocs/rss.ts b/apps/docs/lib/geistdocs/rss.ts new file mode 100644 index 000000000..6567ec7c1 --- /dev/null +++ b/apps/docs/lib/geistdocs/rss.ts @@ -0,0 +1,29 @@ +const FEED_BASELINE_TIMESTAMP = Date.parse("2026-06-17T00:00:00.000Z"); + +interface FeedPage { + data: object; +} + +interface DatedFeedPage { + lastModified: Date; + page: Page; +} + +const getLastModified = (data: object): Date | undefined => + "lastModified" in data && data.lastModified instanceof Date ? data.lastModified : undefined; + +export const selectDatedFeedPages = ( + pages: readonly Page[], +): DatedFeedPage[] => + pages.map((page) => ({ + lastModified: getLastModified(page.data) ?? new Date(FEED_BASELINE_TIMESTAMP), + page, + })); + +export const getFeedUpdatedAt = ( + pages: readonly DatedFeedPage[], +): Date => + pages.reduce( + (latest, { lastModified }) => (lastModified > latest ? lastModified : latest), + new Date(FEED_BASELINE_TIMESTAMP), + );