From 1bfa919b0c402edb49bd73197c1e205a79dc652d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 27 Jan 2026 19:40:02 +0000 Subject: [PATCH] feat(blog): add /blog index page with dynamic rendering - Create blog index page at /blog - Use force-dynamic for request-time publish gating - Require Node.js runtime for filesystem reads - Display posts with title, description, and date - Add empty state when no posts are published - Include tags display (up to 3) Closes: MKT-68 --- apps/web-roo-code/src/app/blog/page.tsx | 112 ++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 apps/web-roo-code/src/app/blog/page.tsx diff --git a/apps/web-roo-code/src/app/blog/page.tsx b/apps/web-roo-code/src/app/blog/page.tsx new file mode 100644 index 0000000000..318d47a640 --- /dev/null +++ b/apps/web-roo-code/src/app/blog/page.tsx @@ -0,0 +1,112 @@ +import type { Metadata } from "next" +import Link from "next/link" +import { SEO } from "@/lib/seo" +import { ogImageUrl } from "@/lib/og" +import { getAllBlogPosts, formatPostDatePt } from "@/lib/blog" + +// Force dynamic rendering to evaluate publish gating at request-time +export const dynamic = "force-dynamic" + +// Require Node.js runtime for filesystem reads +export const runtime = "nodejs" + +const TITLE = "Blog" +const DESCRIPTION = + "Insights on AI-powered software development, engineering workflows, and the future of coding with Roo Code." +const PATH = "/blog" + +export const metadata: Metadata = { + title: TITLE, + description: DESCRIPTION, + alternates: { + canonical: `${SEO.url}${PATH}`, + }, + openGraph: { + title: TITLE, + description: DESCRIPTION, + url: `${SEO.url}${PATH}`, + siteName: SEO.name, + images: [ + { + url: ogImageUrl(TITLE, DESCRIPTION), + width: 1200, + height: 630, + alt: TITLE, + }, + ], + locale: SEO.locale, + type: "website", + }, + twitter: { + card: SEO.twitterCard, + title: TITLE, + description: DESCRIPTION, + images: [ogImageUrl(TITLE, DESCRIPTION)], + }, + keywords: [...SEO.keywords, "blog", "engineering blog", "AI development"], +} + +export default function BlogIndex() { + // Get all published posts (sorted newest first) + const posts = getAllBlogPosts() + + return ( +
+
+ {/* Page Header */} +
+

Blog

+

{DESCRIPTION}

+
+ + {/* Posts List */} + {posts.length === 0 ? ( + + ) : ( +
+ {posts.map((post) => ( +
+ +

+ {post.title} +

+

{post.description}

+
+ + {post.tags.length > 0 && ( + <> + +
+ {post.tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} +
+ + )} +
+ +
+ ))} +
+ )} +
+
+ ) +} + +function EmptyState() { + return ( +
+

No posts yet

+

Check back soon for new content.

+
+ ) +}