Skip to content

feat(blog): add blog functionality with multiple entries and categories - #748

Merged
smakosh merged 3 commits into
mainfrom
feat/blog-setup
Sep 6, 2025
Merged

smakosh merged 3 commits into
mainfrom
feat/blog-setup

Conversation

@smakosh

@smakosh smakosh commented Sep 6, 2025

Copy link
Copy Markdown
Member
  • Introduced a new blog collection with a defined schema for blog posts, including fields for title, summary, categories, and images.
  • Created a blog listing page that displays all blog entries with filtering options by category.
  • Added individual blog entry pages with metadata generation and support for Markdown content.
  • Included new blog images and markdown files for initial blog posts, enhancing the content available to users.
  • Updated navigation to include a link to the blog section.

This commit enhances the user experience by providing a dedicated space for blog content, allowing for better engagement and information dissemination.

Summary by CodeRabbit

  • New Features

    • Public Blog: listing, individual posts, category pages, images, metadata, and static generation.
    • Site nav: Blog link added.
    • Quick external links: /docs, /discord, /github, /twitter proxy routes.
  • UI

    • Unified hero/navbar added site-wide (changelog, blog); changelog now uses a sticky date column.
    • Models page: capability badges, Playground action, and responsive layout improvements.
  • Content

    • Published three posts: Introducing LLM Gateway; Self-host LLM Gateway; Custom Providers.

- Introduced a new blog collection with a defined schema for blog posts, including fields for title, summary, categories, and images.
- Created a blog listing page that displays all blog entries with filtering options by category.
- Added individual blog entry pages with metadata generation and support for Markdown content.
- Included new blog images and markdown files for initial blog posts, enhancing the content available to users.
- Updated navigation to include a link to the blog section.

This commit enhances the user experience by providing a dedicated space for blog content, allowing for better engagement and information dissemination.
@smakosh
smakosh requested a review from steebchen September 6, 2025 22:21
@smakosh smakosh self-assigned this Sep 6, 2025
@coderabbitai

coderabbitai Bot commented Sep 6, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a Blog content collection and three markdown posts, implements blog index, category, and slug pages plus a BlogList component and navbar link; updates changelog pages to use HeroRSC and layout tweaks; adds Next.js rewrites; enhances model page UI with capability badges and a playground link.

Changes

Cohort / File(s) Summary of changes
Content collections
apps/ui/content-collections.ts, apps/ui/src/types/content-collections.d.ts
Adds a blog collection schema (id, slug, date, title, summary, optional draft, categories[], optional image) and exposes allBlogs/Blog types; registers blog alongside changelog.
Next.js rewrites
apps/ui/next.config.ts
Adds an async rewrites() returning rules that map /docs, /discord, /github, /twitter to external destinations.
Blog pages (App Router)
apps/ui/src/app/blog/page.tsx, apps/ui/src/app/blog/[slug]/page.tsx, apps/ui/src/app/blog/category/[category]/page.tsx, apps/ui/src/app/blog/category/page.tsx
Implements blog index, post detail (with generateStaticParams and generateMetadata), category listing (with static params and metadata), and a placeholder category index. Uses content-collections, Markdown rendering, HeroRSC, and Footer.
Blog list component
apps/ui/src/components/blog/list.tsx
Adds BlogList component: category nav, responsive card grid, optional images, category chips, truncated summary, and localized dates.
Navbar
apps/ui/src/components/landing/navbar.tsx
Adds a "Blog" item under Resources linking to /blog.
Changelog pages
apps/ui/src/app/changelog/page.tsx, apps/ui/src/app/changelog/[slug]/page.tsx
Replaces AppHeader usage with HeroRSC navbarOnly, adjusts top padding/layout; keeps existing content and metadata logic.
Changelog component layout
apps/ui/src/components/changelog/changelog.tsx
Removes AppHeader, adds top padding, refactors header text, and makes the date column sticky on md+ screens.
Models page UI updates
apps/ui/src/app/models/[name]/page.tsx
UI changes: use modelDef.name for headings, add capability badges (Streaming, Vision, Tools, Reasoning, Image Generation), add Playground link/button, and adjust layout for wrapping.
Blog content files
apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md, apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md, apps/ui/src/content/blog/2025-05-10-custom-providers.md
Adds three markdown blog posts with front matter (id, slug, date, title, summary, categories, image metadata) and content.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant NextApp as Next.js App Router
  participant CC as content-collections
  participant Markdown as Markdown renderer
  participant UI as HeroRSC / BlogList / Footer

  User->>NextApp: Request /blog/[slug]
  activate NextApp
  NextApp->>CC: fetch allBlogs()
  CC-->>NextApp: entries[]
  NextApp->>NextApp: find entry by slug or notFound()
  NextApp->>Markdown: render entry.content
  Markdown-->>NextApp: HTML
  NextApp->>UI: Render HeroRSC + Article + Footer
  UI-->>User: HTML response
  deactivate NextApp
Loading
sequenceDiagram
  autonumber
  actor User
  participant NextApp as Next.js App Router
  participant CC as content-collections
  participant UI as HeroRSC / BlogList

  User->>NextApp: Request /blog/category/{category}
  NextApp->>CC: fetch allBlogs()
  CC-->>NextApp: entries[]
  NextApp->>NextApp: filter !draft, match category, sort desc
  NextApp->>UI: Render HeroRSC + BlogList(selectedCategory)
  UI-->>User: HTML response
Loading
sequenceDiagram
  autonumber
  actor User
  participant Next as Next.js Runtime
  participant Rewrites as rewrites()
  participant External as External site

  User->>Next: GET /docs | /discord | /github | /twitter
  Next->>Rewrites: match path
  Rewrites-->>Next: destination URL
  Next->>External: proxy request to destination
  External-->>User: Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • steebchen
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/blog-setup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@smakosh
smakosh enabled auto-merge September 6, 2025 22:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ui/src/components/changelog/changelog.tsx (1)

75-83: Guard against missing images.

If some entries omit image, this will crash. Add a conditional.

-            <div className="bg-card border border-border rounded-lg overflow-hidden">
-              <Link href={`/changelog/${entry.slug}`} prefetch={true}>
-                <Image
-                  src={entry.image.src}
-                  alt={entry.image.alt}
-                  width={entry.image.width}
-                  height={entry.image.height}
-                  className="w-full h-64 object-cover hover:opacity-90 transition-opacity rounded-lg object-top"
-                />
-              </Link>
-            </div>
+            {entry.image && (
+              <div className="bg-card border border-border rounded-lg overflow-hidden">
+                <Link href={`/changelog/${entry.slug}`} prefetch={true}>
+                  <Image
+                    src={entry.image.src}
+                    alt={entry.image.alt || entry.title}
+                    width={entry.image.width || 1200}
+                    height={entry.image.height || 630}
+                    className="w-full h-64 object-cover hover:opacity-90 transition-opacity rounded-lg object-top"
+                  />
+                </Link>
+              </div>
+            )}
🧹 Nitpick comments (15)
apps/ui/src/app/models/[name]/page.tsx (2)

26-33: Align params typing with Next.js App Router conventions

params is synchronous in App Router. Typing it as Promise forces unnecessary await and fights Next types.

-interface PageProps {
-  params: Promise<{ name: string }>;
-}
+interface PageProps {
+  params: { name: string };
+}

-export default async function ModelPage({ params }: PageProps) {
-  const { name } = await params;
+export default async function ModelPage({ params }: PageProps) {
+  const { name } = params;

-export async function generateMetadata({ params }: PageProps) {
-  const { name } = await params;
+export async function generateMetadata({ params }: PageProps) {
+  const { name } = params;

If you’re on a bleeding-edge Next version that intentionally passes a promise here, keep as-is and disregard.

Also applies to: 268-271


115-124: Good: uses next/link for navigation in apps/ui

Link + encoded query is correct per our guidelines. Minor nit: prefetch is true by default; you can omit it if not tuning behavior.

apps/ui/src/components/landing/navbar.tsx (1)

122-136: Don’t wrap mailto: links with next/link.

Using next/link for mailto: can trigger prefetch warnings and is unnecessary. Render an <a> for mailto: (and other external schemes) and use Link for internal routes.

Apply conditionals in both desktop and mobile resource lists:

-                    <NavigationMenuLink asChild>
-                      <Link
-                        href={item.href}
-                        className="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
-                        prefetch={true}
-                      >
-                        <div className="text-sm font-medium leading-none">
-                          {item.name}
-                        </div>
-                      </Link>
-                    </NavigationMenuLink>
+                    <NavigationMenuLink asChild>
+                      {item.href.startsWith("mailto:") ? (
+                        <a
+                          href={item.href}
+                          className="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
+                          aria-label={item.name}
+                        >
+                          <div className="text-sm font-medium leading-none">
+                            {item.name}
+                          </div>
+                        </a>
+                      ) : (
+                        <Link
+                          href={item.href}
+                          className="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
+                          prefetch={true}
+                        >
+                          <div className="text-sm font-medium leading-none">
+                            {item.name}
+                          </div>
+                        </Link>
+                      )}
+                    </NavigationMenuLink>
-              <Link
-                href={item.href}
-                className="text-muted-foreground hover:text-accent-foreground block duration-150"
-                prefetch={true}
-              >
-                {item.name}
-              </Link>
+              {item.href.startsWith("mailto:") ? (
+                <a
+                  href={item.href}
+                  className="text-muted-foreground hover:text-accent-foreground block duration-150"
+                  aria-label={item.name}
+                >
+                  {item.name}
+                </a>
+              ) : (
+                <Link
+                  href={item.href}
+                  className="text-muted-foreground hover:text-accent-foreground block duration-150"
+                  prefetch={true}
+                >
+                  {item.name}
+                </Link>
+              )}

Also applies to: 175-185

apps/ui/src/app/changelog/[slug]/page.tsx (1)

12-20: Type params as an object, not a Promise.

Next.js passes params as a plain object; remove the Promise type and await.

-interface ChangelogEntryPageProps {
-  params: Promise<{ slug: string }>;
-}
+interface ChangelogEntryPageProps {
+  params: { slug: string };
+}
@@
-export default async function ChangelogEntryPage({
-  params,
-}: ChangelogEntryPageProps) {
-  const { slug } = await params;
+export default async function ChangelogEntryPage({ params }: ChangelogEntryPageProps) {
+  const { slug } = params;
@@
-export async function generateMetadata({ params }: ChangelogEntryPageProps) {
-  const { slug } = await params;
+export async function generateMetadata({ params }: ChangelogEntryPageProps) {
+  const { slug } = params;

Also applies to: 93-101

apps/ui/src/components/changelog/changelog.tsx (1)

16-16: Replace non-standard pt-30.

Use a valid Tailwind spacing token or an arbitrary value.

-    <div className="bg-background text-foreground min-h-screen font-sans pt-30">
+    <div className="bg-background text-foreground min-h-screen font-sans pt-28">
apps/ui/content-collections.ts (1)

24-45: Harden blog schema: enforce date/slug formats and non-empty strings.

Prevents malformed content and aligns with UI assumptions.

Apply this diff:

 const blog = defineCollection({
   name: "blog",
   directory: "src/content/blog",
   include: "**/*.md",
   schema: z.object({
     id: z.string(),
-    slug: z.string(),
-    date: z.string(),
+    slug: z.string().regex(/^[a-z0-9-]+$/, "Use lowercase, digits, and hyphens only"),
+    date: z
+      .string()
+      .regex(/^\d{4}-\d{2}-\d{2}$/, "Use YYYY-MM-DD"),
     title: z.string(),
     summary: z.string(),
     draft: z.boolean().optional(),
-    categories: z.array(z.string()).default([]),
+    categories: z.array(z.string().min(1)).default([]),
     image: z
       .object({
         src: z.string(),
-        alt: z.string(),
+        alt: z.string().min(1),
         width: z.number(),
         height: z.number(),
       })
       .optional(),
   }),
 });
apps/ui/src/app/blog/category/page.tsx (1)

1-3: Avoid empty route: redirect to /blog.

Prevents a thin 200 page and improves UX/SEO.

Apply this diff:

-export default function Page() {
-  return null;
-}
+import { redirect } from "next/navigation";
+export default function Page() {
+  redirect("/blog");
+}
apps/ui/src/app/blog/page.tsx (1)

35-50: Optional: make metadata static and typed.

Minor DX improvement; current code is fine.

Apply this diff if you prefer a static export:

-export async function generateMetadata() {
-  return {
+export const metadata = {
     title: "Blog - LLM Gateway",
     description: "News, tutorials, and deep-dives from the LLM Gateway team.",
     openGraph: {
       title: "Blog - LLM Gateway",
       description: "News, tutorials, and deep-dives from the LLM Gateway team.",
       type: "website",
     },
     twitter: {
       card: "summary_large_image",
       title: "Blog - LLM Gateway",
       description: "News, tutorials, and deep-dives from the LLM Gateway team.",
     },
-  };
-}
+} as const;
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md (1)

42-48: Fix bare URLs and replace the raw GitHub link. Wrap each localhost endpoint in a markdown link and point to the public docs domain:

- Web: http://localhost:3002
- Docs: http://localhost:3005
- API: http://localhost:4002
- Gateway: http://localhost:4001
+ Web: [http://localhost:3002](http://localhost:3002)
+ Docs: [http://localhost:3005](http://localhost:3005)
+ API: [http://localhost:4002](http://localhost:4002)
+ Gateway: [http://localhost:4001](http://localhost:4001)

- See the full guide: [`Self Host`](https://raw.githubusercontent.com/theopenco/llmgateway/refs/heads/main/apps/docs/content/self-host.mdx).
+ See the full guide: [Self Host](https://docs.llmgateway.com/self-host).

If you’d rather link to /docs/self-host, adjust your Next.js rewrite in apps/ui/next.config.ts to match dynamic routes (e.g. source: '/docs/:path*').

apps/ui/src/components/blog/list.tsx (4)

23-28: Deduplicate slugify utility.

slugify is duplicated across files. Consider extracting to a shared util to avoid drift.


43-45: Make category pills data-driven.

Derive categories from content (and append “Changelog”) to avoid manual upkeep.

- const categoryList = ["Announcements", "Guides", "Engineering", "Changelog"];
+ const derived = Array.from(
+   new Set(blogEntries.flatMap((e) => e.categories ?? [])),
+ ).filter((c) => c && c !== "Changelog");
+ const categoryList = [...derived.sort(), "Changelog"];

56-83: Add aria-current for the active filter.

Improves accessibility for keyboard/screen-reader users.

- <Link
+ <Link
   href="/blog"
   prefetch={true}
-  className={`px-4 py-2 rounded-full text-sm border transition-colors ${!selectedCategory ? "bg-foreground text-background" : "border-border hover:bg-muted"}`}
+  aria-current={!selectedCategory ? "page" : undefined}
+  className={`px-4 py-2 rounded-full text-sm border transition-colors ${!selectedCategory ? "bg-foreground text-background" : "border-border hover:bg-muted"}`}
 >
@@
- <Link
+ <Link
   key={cat}
   href={
     cat === "Changelog"
       ? "/changelog"
       : `/blog/category/${encodeURIComponent(catSlug)}`
   }
   prefetch={true}
+  aria-current={active ? "page" : undefined}
   className={`px-4 py-2 rounded-full text-sm border transition-colors ${active ? "bg-foreground text-background" : "border-border hover:bg-muted"}`}
 >

137-143: Use a time element for dates.

Better semantics and machine-readability.

- <div className="text-xs text-muted-foreground">
-   {new Date(entry.date).toLocaleDateString("en-US", {
-     year: "numeric",
-     month: "long",
-     day: "numeric",
-   })}
- </div>
+ <time
+   className="text-xs text-muted-foreground"
+   dateTime={entry.date}
+ >
+   {new Date(entry.date).toLocaleDateString("en-US", {
+     year: "numeric",
+     month: "long",
+     day: "numeric",
+   })}
+ </time>
apps/ui/src/app/blog/category/[category]/page.tsx (1)

52-59: Exclude drafts from static params.

Prevents generating category routes based solely on draft posts.

- const { allBlogs } = (await import("content-collections")) as any;
+ const { allBlogs } = await import("content-collections");
@@
- for (const post of allBlogs as any[]) {
-   (post.categories || []).forEach((c: string) => slugs.add(slugify(c)));
+ for (const post of allBlogs.filter((p) => !p?.draft)) {
+   (post.categories ?? []).forEach((c) => slugs.add(slugify(c)));
apps/ui/src/app/blog/[slug]/page.tsx (1)

99-116: Consistent OpenGraph images object format (optional).

Use object form for both primary and fallback for consistency.

- images: entry.image
-   ? [
-       {
-         url: entry.image.src,
-         width: entry.image.width || 800,
-         height: entry.image.height || 400,
-         alt: entry.image.alt || entry.title,
-       },
-     ]
-   : ["/opengraph.png"],
+ images: [
+   entry.image
+     ? {
+         url: entry.image.src,
+         width: entry.image.width || 800,
+         height: entry.image.height || 400,
+         alt: entry.image.alt || entry.title,
+       }
+     : { url: "/opengraph.png", width: 1200, height: 630, alt: "LLM Gateway" },
+ ],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb02c72 and a13fd4c.

⛔ Files ignored due to path filters (3)
  • apps/ui/public/blog/blog-introducing-llm-gateway.png is excluded by !**/*.png
  • apps/ui/public/blog/custom-openai-compatible-providers.png is excluded by !**/*.png
  • apps/ui/public/blog/how-to-self-host-llm-gateway.png is excluded by !**/*.png
📒 Files selected for processing (15)
  • apps/ui/content-collections.ts (1 hunks)
  • apps/ui/next.config.ts (1 hunks)
  • apps/ui/src/app/blog/[slug]/page.tsx (1 hunks)
  • apps/ui/src/app/blog/category/[category]/page.tsx (1 hunks)
  • apps/ui/src/app/blog/category/page.tsx (1 hunks)
  • apps/ui/src/app/blog/page.tsx (1 hunks)
  • apps/ui/src/app/changelog/[slug]/page.tsx (2 hunks)
  • apps/ui/src/app/changelog/page.tsx (2 hunks)
  • apps/ui/src/app/models/[name]/page.tsx (4 hunks)
  • apps/ui/src/components/blog/list.tsx (1 hunks)
  • apps/ui/src/components/changelog/changelog.tsx (2 hunks)
  • apps/ui/src/components/landing/navbar.tsx (1 hunks)
  • apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md (1 hunks)
  • apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md (1 hunks)
  • apps/ui/src/content/blog/2025-05-10-custom-providers.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/components/changelog/changelog.tsx
  • apps/ui/content-collections.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
apps/ui/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In apps/ui (a tanstack router project), always use navigate() for navigation

Files:

  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/components/changelog/changelog.tsx
  • apps/ui/content-collections.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never use as any or : any in TypeScript files.

Files:

  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/components/changelog/changelog.tsx
  • apps/ui/content-collections.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
apps/{ui,docs}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/components/changelog/changelog.tsx
  • apps/ui/content-collections.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
apps/ui/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/ui/**/*.{ts,tsx,js,jsx}: In apps/ui (Next.js App Router), use next/link for links
In apps/ui, use next/navigation router.push/replace or redirect() for programmatic navigation
In apps/ui, never use window.location for navigation

Files:

  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/components/changelog/changelog.tsx
  • apps/ui/content-collections.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
apps/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/ui/**/*.{ts,tsx}: Use Next.js App Router (React Server Components) for the UI
Use next/link for links and next/navigation's router for programmatic navigation
Use TanStack Query for client/state data fetching and caching in the UI
Use Radix UI components with Tailwind CSS for UI building and styling
Prefer localStorage over cookies for client-side data persistence

Files:

  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/components/changelog/changelog.tsx
  • apps/ui/content-collections.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • apps/ui/content-collections.ts
  • apps/ui/next.config.ts
🧠 Learnings (3)
📚 Learning: 2025-09-02T00:39:46.758Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T00:39:46.758Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use Next.js App Router (React Server Components) for the UI

Applied to files:

  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
📚 Learning: 2025-09-02T00:39:46.758Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T00:39:46.758Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use next/link for links and next/navigation's router for programmatic navigation

Applied to files:

  • apps/ui/next.config.ts
  • apps/ui/src/app/changelog/[slug]/page.tsx
📚 Learning: 2025-08-29T15:31:07.077Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:07.077Z
Learning: Applies to apps/ui/**/*.{ts,tsx,js,jsx} : In apps/ui (Next.js App Router), use next/link for links

Applied to files:

  • apps/ui/src/app/changelog/[slug]/page.tsx
🧬 Code graph analysis (6)
apps/ui/src/app/blog/page.tsx (2)
apps/ui/src/components/landing/hero-rsc.tsx (1)
  • HeroRSC (4-10)
apps/ui/src/components/blog/list.tsx (1)
  • BlogList (37-153)
apps/ui/src/app/changelog/page.tsx (1)
apps/ui/src/components/landing/hero-rsc.tsx (1)
  • HeroRSC (4-10)
apps/ui/src/components/blog/list.tsx (1)
apps/ui/src/components/landing/footer.tsx (1)
  • Footer (10-217)
apps/ui/src/app/blog/category/[category]/page.tsx (2)
apps/ui/src/components/landing/hero-rsc.tsx (1)
  • HeroRSC (4-10)
apps/ui/src/components/blog/list.tsx (1)
  • BlogList (37-153)
apps/ui/src/app/blog/[slug]/page.tsx (3)
apps/ui/src/components/landing/hero-rsc.tsx (1)
  • HeroRSC (4-10)
apps/ui/src/lib/utils/markdown.tsx (1)
  • getMarkdownOptions (126-192)
apps/ui/src/components/landing/footer.tsx (1)
  • Footer (10-217)
apps/ui/src/app/changelog/[slug]/page.tsx (2)
apps/ui/src/components/landing/hero-rsc.tsx (1)
  • HeroRSC (4-10)
apps/ui/src/lib/utils/markdown.tsx (1)
  • getMarkdownOptions (126-192)
🪛 LanguageTool
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md

[grammar] ~42-~42: There might be a mistake here.
Context: ...### Access - Web: http://localhost:3002 - Docs: http://localhost:3005 - API: http:...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...lhost:3002 - Docs: http://localhost:3005 - API: http://localhost:4002 - Gateway: ht...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...alhost:3005 - API: http://localhost:4002 - Gateway: http://localhost:4001 See the ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md

42-42: Bare URL used

(MD034, no-bare-urls)


43-43: Bare URL used

(MD034, no-bare-urls)


44-44: Bare URL used

(MD034, no-bare-urls)


45-45: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (10)
apps/ui/src/app/models/[name]/page.tsx (4)

89-90: Good: user-facing title now reflects modelDef.name

Improves clarity when IDs differ from display names.


95-124: Good: header row wraps on small screens

flex-wrap on the badge/actions row prevents overflow and improves responsiveness.


235-236: Good: providers section references display name

Consistent with the header; better UX.


239-240: Copy tweak LGTM

Shorter explanation reads cleaner while preserving meaning.

apps/ui/src/components/landing/navbar.tsx (1)

31-31: Blog nav item — LGTM.

Adds the route cleanly and uses next/link as expected.

apps/ui/src/app/changelog/page.tsx (1)

4-4: Header switch to HeroRSC — LGTM.

Consistent with other pages; no data-path changes.

Also applies to: 17-19

apps/ui/src/app/changelog/[slug]/page.tsx (1)

28-30: Fix invalid Tailwind spacing and guard Next/Image dimensions

  • No tailwind.config.js in the repo—pt-30 isn’t a default spacing token; replace with pt-28, pt-32, or an arbitrary value (e.g. pt-[7.5rem]).
  • Verify that entry.image.width/height on the imported Changelog type from content-collections are always defined; if they’re optional, provide sensible defaults or conditionally render the <Image> to avoid runtime errors.
apps/ui/src/components/changelog/changelog.tsx (1)

44-55: Nice sticky date block — LGTM.

Good UX detail and readable formatting.

apps/ui/content-collections.ts (1)

48-49: LGTM: blog collection added to config.

Configuration correctly includes the new collection.

apps/ui/src/content/blog/2025-05-10-custom-providers.md (1)

1-13: Front matter matches schema.

Post metadata aligns with the defined blog schema.

Comment thread apps/ui/next.config.ts Outdated
Comment thread apps/ui/src/app/blog/[slug]/page.tsx
Comment thread apps/ui/src/app/blog/[slug]/page.tsx
Comment thread apps/ui/src/app/blog/[slug]/page.tsx
Comment thread apps/ui/src/app/blog/[slug]/page.tsx
Comment thread apps/ui/src/app/blog/page.tsx
Comment on lines +7 to +16
import {
AlertTriangle,
Play,
Zap,
Eye,
Wrench,
MessageSquare,
ImagePlus,
} from "lucide-react";
import Link from "next/link";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Import LucideIcon type to eliminate downstream any usage

Bring in the icon type so the capability items can be strongly typed.

 import {
   AlertTriangle,
   Play,
   Zap,
   Eye,
   Wrench,
   MessageSquare,
   ImagePlus,
 } from "lucide-react";
+import type { LucideIcon } from "lucide-react";
 import Link from "next/link";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {
AlertTriangle,
Play,
Zap,
Eye,
Wrench,
MessageSquare,
ImagePlus,
} from "lucide-react";
import Link from "next/link";
import {
AlertTriangle,
Play,
Zap,
Eye,
Wrench,
MessageSquare,
ImagePlus,
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import Link from "next/link";
🤖 Prompt for AI Agents
In apps/ui/src/app/models/[name]/page.tsx around lines 7 to 16, the Lucide icon
imports are untyped leading to downstream any usage; import the LucideIcon type
from "lucide-react" (e.g. import type { LucideIcon } from "lucide-react") and
annotate the capability items/props with LucideIcon so the icon fields are
strongly typed (replace any occurrences of implicit any for icon props or
capability items with LucideIcon).

Comment thread apps/ui/src/app/models/[name]/page.tsx
Comment on lines +9 to +12
src: "/blog/blog-introducing-llm-gateway.png"
alt: "LLM Gateway"
width: 2282
height: 1198

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify asset path and docs link resolve at runtime.

Ensure the image exists under the app’s public assets and that “/docs” resolves (page or rewrite).

Also applies to: 46-46


🏁 Script executed:

#!/bin/bash
# 1) Image exists somewhere in the repo (ideally apps/ui/public/blog/)
fd -HI --strip-cwd-prefix 'blog-introducing-llm-gateway.png'

# 2) Is there a local /docs route or a rewrite?
rg -nP -g 'apps/ui/**' '(href=.|Link.*href=.)["'\'']/docs["'\'']'
rg -nP 'rewrites\s*\(' next.config.* apps/ui/**/next.config.* 2>/dev/null || true

Length of output: 256


Configure /docs route or update link
I confirmed the image at /blog/blog-introducing-llm-gateway.png exists under apps/ui/public/blog, but I didn’t find any /docs page or rewrite in the UI app—this link will 404. Add a /docs route or a rewrite entry.
Location: apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md (lines 9–12, 46)

Comment thread apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md
@smakosh
smakosh added this pull request to the merge queue Sep 6, 2025
@steebchen
steebchen removed this pull request from the merge queue due to a manual request Sep 6, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a13fd4c and c79759f.

📒 Files selected for processing (1)
  • apps/ui/src/types/content-collections.d.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/ui/src/types/content-collections.d.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • apps/ui/src/types/content-collections.d.ts
apps/ui/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In apps/ui (a tanstack router project), always use navigate() for navigation

Files:

  • apps/ui/src/types/content-collections.d.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never use as any or : any in TypeScript files.

Files:

  • apps/ui/src/types/content-collections.d.ts
apps/{ui,docs}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/ui/src/types/content-collections.d.ts
apps/ui/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/ui/**/*.{ts,tsx,js,jsx}: In apps/ui (Next.js App Router), use next/link for links
In apps/ui, use next/navigation router.push/replace or redirect() for programmatic navigation
In apps/ui, never use window.location for navigation

Files:

  • apps/ui/src/types/content-collections.d.ts
apps/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/ui/**/*.{ts,tsx}: Use Next.js App Router (React Server Components) for the UI
Use next/link for links and next/navigation's router for programmatic navigation
Use TanStack Query for client/state data fetching and caching in the UI
Use Radix UI components with Tailwind CSS for UI building and styling
Prefer localStorage over cookies for client-side data persistence

Files:

  • apps/ui/src/types/content-collections.d.ts
🔇 Additional comments (1)
apps/ui/src/types/content-collections.d.ts (1)

20-33: Update Blog interface to match schema and usage
Reflect the Zod schema’s draft (optional) and categories (defaulted) fields, and make image optional to align with optional chaining in consumers.

--- a/apps/ui/src/types/content-collections.d.ts
+++ b/apps/ui/src/types/content-collections.d.ts
@@ export interface Blog {
-  image: {
+  categories: string[];
+  draft?: boolean;
+  image?: {
     src: string;
     alt: string;
     width: number;
     height: number;
   };

Comment thread apps/ui/src/types/content-collections.d.ts
@smakosh
smakosh enabled auto-merge September 6, 2025 22:40
@smakosh
smakosh added this pull request to the merge queue Sep 6, 2025
Merged via the queue into main with commit 95293b9 Sep 6, 2025
8 checks passed
@smakosh
smakosh deleted the feat/blog-setup branch September 6, 2025 22:42
@coderabbitai coderabbitai Bot mentioned this pull request Jan 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants